llm-layered-defense 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. llm_layered_defense-0.2.0/.gitignore +170 -0
  2. llm_layered_defense-0.2.0/CHANGELOG.md +38 -0
  3. llm_layered_defense-0.2.0/LICENSE +21 -0
  4. llm_layered_defense-0.2.0/PKG-INFO +190 -0
  5. llm_layered_defense-0.2.0/README.md +151 -0
  6. llm_layered_defense-0.2.0/pyproject.toml +66 -0
  7. llm_layered_defense-0.2.0/src/layered_defense/__init__.py +84 -0
  8. llm_layered_defense-0.2.0/src/layered_defense/adapters/__init__.py +5 -0
  9. llm_layered_defense-0.2.0/src/layered_defense/adapters/langchain.py +34 -0
  10. llm_layered_defense-0.2.0/src/layered_defense/adapters/tools.py +53 -0
  11. llm_layered_defense-0.2.0/src/layered_defense/cli.py +203 -0
  12. llm_layered_defense-0.2.0/src/layered_defense/corpus.py +240 -0
  13. llm_layered_defense-0.2.0/src/layered_defense/data/documents_corpus.csv +121 -0
  14. llm_layered_defense-0.2.0/src/layered_defense/data/rag_qa_samples.csv +501 -0
  15. llm_layered_defense-0.2.0/src/layered_defense/evaluation.py +123 -0
  16. llm_layered_defense-0.2.0/src/layered_defense/experiment.py +161 -0
  17. llm_layered_defense-0.2.0/src/layered_defense/guard.py +327 -0
  18. llm_layered_defense-0.2.0/src/layered_defense/llm.py +234 -0
  19. llm_layered_defense-0.2.0/src/layered_defense/pipeline.py +147 -0
  20. llm_layered_defense-0.2.0/src/layered_defense/prompts.py +133 -0
  21. llm_layered_defense-0.2.0/src/layered_defense/providers.py +359 -0
  22. llm_layered_defense-0.2.0/src/layered_defense/report.py +153 -0
  23. llm_layered_defense-0.2.0/src/layered_defense/retrieval.py +134 -0
  24. llm_layered_defense-0.2.0/src/layered_defense/verifier.py +167 -0
  25. llm_layered_defense-0.2.0/tests/conftest.py +14 -0
  26. llm_layered_defense-0.2.0/tests/test_corpus.py +48 -0
  27. llm_layered_defense-0.2.0/tests/test_experiment_and_report.py +94 -0
  28. llm_layered_defense-0.2.0/tests/test_guard.py +206 -0
  29. llm_layered_defense-0.2.0/tests/test_pipeline.py +55 -0
  30. llm_layered_defense-0.2.0/tests/test_prompts.py +34 -0
  31. llm_layered_defense-0.2.0/tests/test_providers.py +80 -0
@@ -0,0 +1,170 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script to a directory where the package
31
+ # is being installed etc.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tosca/
42
+ .pytest_cache/
43
+ .unittest_cache/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .nox/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or app, you might want to share your .python-version if you
87
+ # use pyenv and want other developers to use the same version of Python
88
+ #.python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if used with a venv it might be ignored.
93
+ #Pipfile.lock
94
+
95
+ # poetry
96
+ # Similar to Pipfile.lock, poetry.lock is generally recommended to be committed.
97
+ #poetry.lock
98
+
99
+ # pdm
100
+ # Similar to Pipfile.lock, pdm.lock is generally recommended to be committed.
101
+ #.pdm-python
102
+ #.pdm-build/
103
+
104
+ # PEP 582; dependency directory created by pipflow via profile, pdm, etc.
105
+ __pypackages__/
106
+
107
+ # Celery stuff
108
+ celerybeat-schedule
109
+ celerybeat.pid
110
+
111
+ # SageMath vars
112
+ *.sage.py
113
+
114
+ # Environments
115
+ .env
116
+ .venv
117
+ env/
118
+ venv/
119
+ ENV/
120
+ env.bak/
121
+ venv.bak/
122
+
123
+ # Spyder project settings
124
+ .spyderproject
125
+ .spyproject
126
+
127
+ # Rope project settings
128
+ .ropeproject
129
+
130
+ # mkdocs documentation
131
+ /site
132
+
133
+ # mypy
134
+ .mypy_cache/
135
+ .dmypy.json
136
+ dmypy.json
137
+
138
+ # Pyre type checker
139
+ .pyre/
140
+
141
+ # pytype
142
+ .pytype/
143
+
144
+ # Cython debug symbols
145
+ cython_debug/
146
+
147
+ # OS-specific files
148
+ .DS_Store
149
+ .DS_Store?
150
+ ._*
151
+ .Spotlight-V100
152
+ .Trashes
153
+ ehthumbs.db
154
+ Thumbs.db
155
+
156
+ # Project-specific ignored files
157
+ data/
158
+ results.json
159
+
160
+ # project
161
+ .venv/
162
+ results*.json
163
+ paper.aux
164
+ paper.log
165
+ paper.out
166
+ paper.fdb_latexmk
167
+ paper.fls
168
+ submission.zip
169
+ .DS_Store
170
+ pdf-forge-exports/
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 - 2026-10-07
4
+
5
+ Runtime guard for agents (phase one).
6
+
7
+ * `LayeredDefense` / `Guard`: wrap any LLM call with retrieval, prompt
8
+ hardening, generation and claim-level verification; sync `answer()`,
9
+ async `aanswer()`, `stream()` (tokens, then verdict and final answer) and
10
+ standalone `verify()` for answers produced elsewhere.
11
+ * Bring your own LLM: `"provider:model"` strings, plain callables (sync or
12
+ async), `OpenAILLM`, `AnthropicLLM`, `LangChainLLM`, or any `.chat()` object.
13
+ * `GuardPolicy`: rewrite / refuse / flag / allow per claim class, risk-tiered
14
+ verification, parametric-knowledge mode, context budget, persona and prompt.
15
+ * Pluggable retrieval (`Retriever` protocol, `CallableRetriever`,
16
+ `LexicalRetriever`) and `as_documents()` so tool outputs, dicts and
17
+ LangChain documents can be passed as evidence.
18
+ * Adapters: tool definitions and functions for OpenAI/Anthropic tool calling,
19
+ LangChain `as_runnable()`.
20
+ * `GuardEvent` callbacks for tracing; verifier claims carry evidence ids.
21
+
22
+ ## 0.1.0 - 2026-10-07
23
+
24
+ First packaged release, replacing the three loose scripts.
25
+
26
+ * `layered_defense` package with corpus/retrieval, prompt assembly, providers,
27
+ pipeline, LLM judge, ablation runner, reporter and `layered-defense` CLI.
28
+ * Removed the simulation fallback that fabricated results calibrated to the
29
+ paper's own numbers whenever an API call failed; failures are now recorded.
30
+ * Closed-book prompts no longer force a refusal through the "stay inside the
31
+ documents" constraint.
32
+ * Metadata recency window is relative to the current (or a given) year.
33
+ * Retrieval tokenises properly (punctuation, stop-words) and follows the
34
+ paper's retrieve-20 / filter / re-rank-to-k scheme.
35
+ * Added the poisoned-index control conditions so Layer 2 is measured.
36
+ * Summary statistics carry `n`, error counts, Wilson 95 % intervals, refusal
37
+ and correction rates; reporter emits data-driven observations.
38
+ * Bundled the synthetic corpus and QA data; no hard-coded absolute paths.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rajesh Govindarajan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.5
2
+ Name: llm-layered-defense
3
+ Version: 0.2.0
4
+ Summary: Layered defense framework (verifier, metadata-filtered RAG, negative prompting, alignment) for mitigating LLM hallucinations, with a reproducible ablation harness.
5
+ Project-URL: Homepage, https://github.com/rgolabs/Mitigating-Hallucination
6
+ Project-URL: Issues, https://github.com/rgolabs/Mitigating-Hallucination/issues
7
+ Project-URL: Paper, https://doi.org/10.5281/zenodo.23768596
8
+ Project-URL: Changelog, https://github.com/rgolabs/Mitigating-Hallucination/blob/main/CHANGELOG.md
9
+ Author-email: Rajesh Govindarajan <rj@onboardai.org>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai-safety,evaluation,hallucination,llm,llm-as-a-judge,prompt-engineering,rag
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: python-dotenv>=1.0
25
+ Requires-Dist: requests>=2.31
26
+ Provides-Extra: anthropic
27
+ Requires-Dist: anthropic>=0.40; extra == 'anthropic'
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
31
+ Requires-Dist: pytest>=8; extra == 'dev'
32
+ Requires-Dist: ruff>=0.6; extra == 'dev'
33
+ Requires-Dist: twine>=5; extra == 'dev'
34
+ Provides-Extra: langchain
35
+ Requires-Dist: langchain-core>=0.3; extra == 'langchain'
36
+ Provides-Extra: openai
37
+ Requires-Dist: openai>=1.40; extra == 'openai'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # llm-layered-defense
41
+
42
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.23768596.svg)](https://doi.org/10.5281/zenodo.23768596)
43
+
44
+ A small, dependency-light framework and experiment harness for the paper
45
+ *"A Layered Defense Framework for Mitigating Hallucinations in Large Language
46
+ Models"* (Govindarajan, 2026). It implements the four security layers as
47
+ composable pieces and runs the cumulative ablation of Table 5 against any
48
+ OpenAI-compatible chat endpoint.
49
+
50
+ | Layer | Module | What it does |
51
+ |---|---|---|
52
+ | L1 verifier | `layered_defense.pipeline.DefensePipeline.verify` | LLM-as-a-Judge extracts atomic claims, labels them supported / contradicted / unverifiable and rewrites the draft. |
53
+ | L2 metadata-filtered RAG | `layered_defense.corpus` | Lexical top-k retrieval over a document store; a `MetadataPolicy` (domain, recency, trust score) is applied as a hard filter before re-ranking. A poisoned index with 5 % adversarial documents is supported for the context-poisoning experiment. |
54
+ | L3 prompt engineering | `layered_defense.prompts` | System instruction, 3-shot grounded examples and negative constraints (context-aware: closed-book prompts get anti-fabrication constraints instead of a forced refusal). |
55
+ | L4 alignment | `layered_defense.prompts.ALIGNMENT_PERSONAS` + `--base-model/--aligned-model` | Approximated on hosted models by persona + temperature, or faithfully by pointing each level at a different checkpoint. |
56
+
57
+ The harness **never simulates model output**. A failed provider call is
58
+ recorded on the run with an `error` field and excluded from every statistic.
59
+ An offline `mock` provider exists for tests and dry runs; its results are
60
+ tagged and refused by the LaTeX reporter unless `--allow-mock` is passed.
61
+
62
+ ## Use it inside your agent
63
+
64
+ ```python
65
+ from layered_defense import LayeredDefense, GuardPolicy
66
+
67
+ # any LLM: "openrouter:google/gemini-2.5-flash", "openai:gpt-4o", "custom:model"
68
+ # (with base_url), a callable fn(messages) -> str, OpenAILLM(client, model),
69
+ # AnthropicLLM(client, model) or LangChainLLM(chat_model)
70
+ guard = LayeredDefense("openrouter:google/gemini-2.5-flash",
71
+ verifier="openrouter:anthropic/claude-sonnet-4.5",
72
+ system_prompt="You are the support assistant for ACME.")
73
+
74
+ out = guard.answer("Is this customer eligible for a refund?",
75
+ context=[kb_passage, crm_ticket]) # tool outputs as evidence
76
+ out.text # grounded answer (rewritten / refused / flagged per policy)
77
+ out.action # accepted | rewritten | refused | flagged | unverified
78
+ out.claims # [Claim(text, judgment, evidence_ids), ...]
79
+
80
+ await guard.aanswer(...) # async
81
+ for ev in guard.stream(...): ... # token events, then verify/decision/answer
82
+ guard.verify(question, answer, context) # audit an answer produced elsewhere
83
+ ```
84
+
85
+ `GuardPolicy(on_contradicted="refuse", on_unverifiable="flag",
86
+ allow_parametric=True, verify_when=lambda q, docs: is_high_risk(q))` controls
87
+ what happens per claim class and when the verifier runs. Pass a
88
+ `retriever=` (any `retrieve(query, k=, domain=)` object or `fn(query, k)`),
89
+ `metadata_policy=` for Layer 2 filtering, and `on_event=` for tracing.
90
+ Tool-calling adapters live in `layered_defense.adapters`
91
+ (`OPENAI_VERIFY_TOOL`, `ANTHROPIC_VERIFY_TOOL`, `make_verify_tool(guard)`,
92
+ `as_runnable(guard)` for LangChain/LangGraph).
93
+
94
+ ## Install
95
+
96
+ ```bash
97
+ pip install llm-layered-defense
98
+ ```
99
+
100
+ Requires Python 3.10+. A bundled synthetic corpus (120 documents) and QA set
101
+ (500 questions, six domains) ship with the package, so no download is needed.
102
+
103
+ ## Quick start
104
+
105
+ ```bash
106
+ # offline dry run with the deterministic mock provider
107
+ layered-defense demo --n-per-domain 2 --ablation extended
108
+
109
+ # live run through OpenRouter
110
+ export OPENROUTER_API_KEY=...
111
+ layered-defense run --provider openrouter --model google/gemini-2.5-flash \
112
+ --verifier-model anthropic/claude-sonnet-4.5 --n-per-domain 5 --out results.json
113
+
114
+ # any OpenAI-compatible server (vLLM, Ollama, LM Studio, a local proxy, ...)
115
+ layered-defense run --provider custom --base-url http://127.0.0.1:8787/v1 \
116
+ --api-key "$LLM_API_KEY" --model my-model --pricing my-model=1:5
117
+
118
+ # render LaTeX tables (+ data-driven observations) or a Markdown summary
119
+ layered-defense report results.json --out results_tables.tex
120
+ layered-defense report results.json --format markdown
121
+ ```
122
+
123
+ Providers: `openrouter`, `openai`, `gemini` (OpenAI-compatible endpoint),
124
+ `groq`, `ollama`, `local` (`$LOCAL_LLM_BASE_URL` / `$LOCAL_LLM_API_KEY`),
125
+ `custom` (`--base-url`), `mock`. A `.env` file in the working directory is
126
+ loaded automatically.
127
+
128
+ ### Ablations
129
+
130
+ `--ablation table5` runs the five cumulative rows of the paper. `--ablation
131
+ extended` adds two Layer-2 controls between rows 1 and 2, *vanilla RAG on a
132
+ clean index* and *vanilla RAG on the poisoned index*, so the context-poisoning
133
+ effect that motivates metadata filtering is measured rather than assumed.
134
+ `--attack-strength` (0..1) sets how retrieval-optimised the adversarial
135
+ documents are; at 1 they tie with the best clean document.
136
+
137
+ ### What a results file contains
138
+
139
+ `provenance` (models, seed, policy, pricing, timestamps), `summary` (per row:
140
+ `n`, `errors`, hallucination rate with a 95 % Wilson interval, refusal rate,
141
+ verifier-correction rate, share of prompts that contained an adversarial
142
+ document, mean latency, cost per 1k queries, mean helpfulness) and `raw_runs`
143
+ (every prompt's context ids, draft, final answer, verifier claims, judge
144
+ verdict and rationale).
145
+
146
+ ## Library use
147
+
148
+ ```python
149
+ from layered_defense import DefensePipeline, DocCorpus, LayerConfig, MetadataPolicy, make_provider
150
+
151
+ corpus = DocCorpus.from_csv("documents_corpus.csv").inject_adversarial()
152
+ llm = make_provider("openrouter", model="google/gemini-2.5-flash")
153
+ pipe = DefensePipeline(corpus, llm, policy=MetadataPolicy(min_quality=0.7, recency_years=5))
154
+ full_stack = LayerConfig("full", rag="filtered", prompt="full", alignment="aligned", verifier=True)
155
+ result = pipe.answer("What is inflation?", "Finance", full_stack)
156
+ print(result.final_answer, result.corrected, result.latency_s, result.cost_usd)
157
+ ```
158
+
159
+ ## Honest scope
160
+
161
+ * Layer 4 cannot fine-tune anything. With a single hosted model the "base"
162
+ and "aligned" rows differ only by system prompt and temperature. Use
163
+ `--base-model` / `--aligned-model` with real base vs. instruct checkpoints
164
+ for a faithful L4 comparison.
165
+ * Hallucination is judged by an LLM, not by human annotators. Use a judge at
166
+ least as strong as the generator and a different model where possible.
167
+ * Retrieval is lexical (token overlap with stop-word removal), not embedding
168
+ based. Swap `DocCorpus.retrieve` for your own retriever if needed.
169
+ * Small samples give wide intervals. The reporter prints the Wilson interval
170
+ and refuses to claim a trend the data do not show.
171
+
172
+ ## Paper
173
+
174
+ Preprint: Govindarajan, R. (2026). *A Layered Defense Framework for Mitigating Hallucinations in Large Language Models.* Zenodo. https://doi.org/10.5281/zenodo.23768596
175
+
176
+ `paper.tex` includes `results_tables.tex`, which `layered-defense report`
177
+ generates from `results.json`. Build with `latexmk -pdf paper.tex`.
178
+
179
+ ## Development
180
+
181
+ ```bash
182
+ uv venv && uv pip install -e ".[dev]"
183
+ pytest
184
+ ruff check src tests
185
+ python -m build && twine check dist/*
186
+ ```
187
+
188
+ ## License
189
+
190
+ MIT. The bundled corpus and QA set are synthetic (CC0-style educational use).
@@ -0,0 +1,151 @@
1
+ # llm-layered-defense
2
+
3
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.23768596.svg)](https://doi.org/10.5281/zenodo.23768596)
4
+
5
+ A small, dependency-light framework and experiment harness for the paper
6
+ *"A Layered Defense Framework for Mitigating Hallucinations in Large Language
7
+ Models"* (Govindarajan, 2026). It implements the four security layers as
8
+ composable pieces and runs the cumulative ablation of Table 5 against any
9
+ OpenAI-compatible chat endpoint.
10
+
11
+ | Layer | Module | What it does |
12
+ |---|---|---|
13
+ | L1 verifier | `layered_defense.pipeline.DefensePipeline.verify` | LLM-as-a-Judge extracts atomic claims, labels them supported / contradicted / unverifiable and rewrites the draft. |
14
+ | L2 metadata-filtered RAG | `layered_defense.corpus` | Lexical top-k retrieval over a document store; a `MetadataPolicy` (domain, recency, trust score) is applied as a hard filter before re-ranking. A poisoned index with 5 % adversarial documents is supported for the context-poisoning experiment. |
15
+ | L3 prompt engineering | `layered_defense.prompts` | System instruction, 3-shot grounded examples and negative constraints (context-aware: closed-book prompts get anti-fabrication constraints instead of a forced refusal). |
16
+ | L4 alignment | `layered_defense.prompts.ALIGNMENT_PERSONAS` + `--base-model/--aligned-model` | Approximated on hosted models by persona + temperature, or faithfully by pointing each level at a different checkpoint. |
17
+
18
+ The harness **never simulates model output**. A failed provider call is
19
+ recorded on the run with an `error` field and excluded from every statistic.
20
+ An offline `mock` provider exists for tests and dry runs; its results are
21
+ tagged and refused by the LaTeX reporter unless `--allow-mock` is passed.
22
+
23
+ ## Use it inside your agent
24
+
25
+ ```python
26
+ from layered_defense import LayeredDefense, GuardPolicy
27
+
28
+ # any LLM: "openrouter:google/gemini-2.5-flash", "openai:gpt-4o", "custom:model"
29
+ # (with base_url), a callable fn(messages) -> str, OpenAILLM(client, model),
30
+ # AnthropicLLM(client, model) or LangChainLLM(chat_model)
31
+ guard = LayeredDefense("openrouter:google/gemini-2.5-flash",
32
+ verifier="openrouter:anthropic/claude-sonnet-4.5",
33
+ system_prompt="You are the support assistant for ACME.")
34
+
35
+ out = guard.answer("Is this customer eligible for a refund?",
36
+ context=[kb_passage, crm_ticket]) # tool outputs as evidence
37
+ out.text # grounded answer (rewritten / refused / flagged per policy)
38
+ out.action # accepted | rewritten | refused | flagged | unverified
39
+ out.claims # [Claim(text, judgment, evidence_ids), ...]
40
+
41
+ await guard.aanswer(...) # async
42
+ for ev in guard.stream(...): ... # token events, then verify/decision/answer
43
+ guard.verify(question, answer, context) # audit an answer produced elsewhere
44
+ ```
45
+
46
+ `GuardPolicy(on_contradicted="refuse", on_unverifiable="flag",
47
+ allow_parametric=True, verify_when=lambda q, docs: is_high_risk(q))` controls
48
+ what happens per claim class and when the verifier runs. Pass a
49
+ `retriever=` (any `retrieve(query, k=, domain=)` object or `fn(query, k)`),
50
+ `metadata_policy=` for Layer 2 filtering, and `on_event=` for tracing.
51
+ Tool-calling adapters live in `layered_defense.adapters`
52
+ (`OPENAI_VERIFY_TOOL`, `ANTHROPIC_VERIFY_TOOL`, `make_verify_tool(guard)`,
53
+ `as_runnable(guard)` for LangChain/LangGraph).
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install llm-layered-defense
59
+ ```
60
+
61
+ Requires Python 3.10+. A bundled synthetic corpus (120 documents) and QA set
62
+ (500 questions, six domains) ship with the package, so no download is needed.
63
+
64
+ ## Quick start
65
+
66
+ ```bash
67
+ # offline dry run with the deterministic mock provider
68
+ layered-defense demo --n-per-domain 2 --ablation extended
69
+
70
+ # live run through OpenRouter
71
+ export OPENROUTER_API_KEY=...
72
+ layered-defense run --provider openrouter --model google/gemini-2.5-flash \
73
+ --verifier-model anthropic/claude-sonnet-4.5 --n-per-domain 5 --out results.json
74
+
75
+ # any OpenAI-compatible server (vLLM, Ollama, LM Studio, a local proxy, ...)
76
+ layered-defense run --provider custom --base-url http://127.0.0.1:8787/v1 \
77
+ --api-key "$LLM_API_KEY" --model my-model --pricing my-model=1:5
78
+
79
+ # render LaTeX tables (+ data-driven observations) or a Markdown summary
80
+ layered-defense report results.json --out results_tables.tex
81
+ layered-defense report results.json --format markdown
82
+ ```
83
+
84
+ Providers: `openrouter`, `openai`, `gemini` (OpenAI-compatible endpoint),
85
+ `groq`, `ollama`, `local` (`$LOCAL_LLM_BASE_URL` / `$LOCAL_LLM_API_KEY`),
86
+ `custom` (`--base-url`), `mock`. A `.env` file in the working directory is
87
+ loaded automatically.
88
+
89
+ ### Ablations
90
+
91
+ `--ablation table5` runs the five cumulative rows of the paper. `--ablation
92
+ extended` adds two Layer-2 controls between rows 1 and 2, *vanilla RAG on a
93
+ clean index* and *vanilla RAG on the poisoned index*, so the context-poisoning
94
+ effect that motivates metadata filtering is measured rather than assumed.
95
+ `--attack-strength` (0..1) sets how retrieval-optimised the adversarial
96
+ documents are; at 1 they tie with the best clean document.
97
+
98
+ ### What a results file contains
99
+
100
+ `provenance` (models, seed, policy, pricing, timestamps), `summary` (per row:
101
+ `n`, `errors`, hallucination rate with a 95 % Wilson interval, refusal rate,
102
+ verifier-correction rate, share of prompts that contained an adversarial
103
+ document, mean latency, cost per 1k queries, mean helpfulness) and `raw_runs`
104
+ (every prompt's context ids, draft, final answer, verifier claims, judge
105
+ verdict and rationale).
106
+
107
+ ## Library use
108
+
109
+ ```python
110
+ from layered_defense import DefensePipeline, DocCorpus, LayerConfig, MetadataPolicy, make_provider
111
+
112
+ corpus = DocCorpus.from_csv("documents_corpus.csv").inject_adversarial()
113
+ llm = make_provider("openrouter", model="google/gemini-2.5-flash")
114
+ pipe = DefensePipeline(corpus, llm, policy=MetadataPolicy(min_quality=0.7, recency_years=5))
115
+ full_stack = LayerConfig("full", rag="filtered", prompt="full", alignment="aligned", verifier=True)
116
+ result = pipe.answer("What is inflation?", "Finance", full_stack)
117
+ print(result.final_answer, result.corrected, result.latency_s, result.cost_usd)
118
+ ```
119
+
120
+ ## Honest scope
121
+
122
+ * Layer 4 cannot fine-tune anything. With a single hosted model the "base"
123
+ and "aligned" rows differ only by system prompt and temperature. Use
124
+ `--base-model` / `--aligned-model` with real base vs. instruct checkpoints
125
+ for a faithful L4 comparison.
126
+ * Hallucination is judged by an LLM, not by human annotators. Use a judge at
127
+ least as strong as the generator and a different model where possible.
128
+ * Retrieval is lexical (token overlap with stop-word removal), not embedding
129
+ based. Swap `DocCorpus.retrieve` for your own retriever if needed.
130
+ * Small samples give wide intervals. The reporter prints the Wilson interval
131
+ and refuses to claim a trend the data do not show.
132
+
133
+ ## Paper
134
+
135
+ Preprint: Govindarajan, R. (2026). *A Layered Defense Framework for Mitigating Hallucinations in Large Language Models.* Zenodo. https://doi.org/10.5281/zenodo.23768596
136
+
137
+ `paper.tex` includes `results_tables.tex`, which `layered-defense report`
138
+ generates from `results.json`. Build with `latexmk -pdf paper.tex`.
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ uv venv && uv pip install -e ".[dev]"
144
+ pytest
145
+ ruff check src tests
146
+ python -m build && twine check dist/*
147
+ ```
148
+
149
+ ## License
150
+
151
+ MIT. The bundled corpus and QA set are synthetic (CC0-style educational use).
@@ -0,0 +1,66 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "llm-layered-defense"
7
+ version = "0.2.0"
8
+ description = "Layered defense framework (verifier, metadata-filtered RAG, negative prompting, alignment) for mitigating LLM hallucinations, with a reproducible ablation harness."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "Rajesh Govindarajan", email = "rj@onboardai.org" }]
14
+ keywords = ["llm", "hallucination", "rag", "llm-as-a-judge", "prompt-engineering", "ai-safety", "evaluation"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Science/Research",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
26
+ ]
27
+ dependencies = [
28
+ "requests>=2.31",
29
+ "python-dotenv>=1.0",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ openai = ["openai>=1.40"]
34
+ anthropic = ["anthropic>=0.40"]
35
+ langchain = ["langchain-core>=0.3"]
36
+ dev = ["pytest>=8", "pytest-cov>=5", "ruff>=0.6", "build>=1.2", "twine>=5"]
37
+
38
+ [project.scripts]
39
+ layered-defense = "layered_defense.cli:main"
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/rgolabs/Mitigating-Hallucination"
43
+ Issues = "https://github.com/rgolabs/Mitigating-Hallucination/issues"
44
+ Paper = "https://doi.org/10.5281/zenodo.23768596"
45
+ Changelog = "https://github.com/rgolabs/Mitigating-Hallucination/blob/main/CHANGELOG.md"
46
+
47
+ [tool.hatch.build]
48
+ artifacts = ["src/layered_defense/data/*.csv"] # data/ is git-ignored at the repo root
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["src/layered_defense"]
52
+
53
+ [tool.hatch.build.targets.sdist]
54
+ include = ["src/layered_defense", "tests", "README.md", "LICENSE", "CHANGELOG.md", "pyproject.toml"]
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
58
+ addopts = "-q"
59
+
60
+ [tool.ruff]
61
+ line-length = 160
62
+ target-version = "py310"
63
+
64
+ [tool.ruff.lint]
65
+ select = ["E", "F", "I", "UP", "B"]
66
+ ignore = ["E501"] # long prompt/data strings