staleguard 0.1.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.
- staleguard-0.1.0/LICENSE +21 -0
- staleguard-0.1.0/PKG-INFO +361 -0
- staleguard-0.1.0/README.md +309 -0
- staleguard-0.1.0/pyproject.toml +65 -0
- staleguard-0.1.0/setup.cfg +4 -0
- staleguard-0.1.0/src/__init__.py +64 -0
- staleguard-0.1.0/src/adapters/__init__.py +11 -0
- staleguard-0.1.0/src/adapters/chroma.py +50 -0
- staleguard-0.1.0/src/adapters/json_adapter.py +67 -0
- staleguard-0.1.0/src/adapters/langchain.py +34 -0
- staleguard-0.1.0/src/alternatives/__init__.py +3 -0
- staleguard-0.1.0/src/alternatives/finder.py +35 -0
- staleguard-0.1.0/src/api.py +135 -0
- staleguard-0.1.0/src/audit_types.py +11 -0
- staleguard-0.1.0/src/auditor.py +444 -0
- staleguard-0.1.0/src/chroma_demo.py +143 -0
- staleguard-0.1.0/src/cli.py +60 -0
- staleguard-0.1.0/src/config.py +31 -0
- staleguard-0.1.0/src/conflicts/__init__.py +3 -0
- staleguard-0.1.0/src/conflicts/nli.py +68 -0
- staleguard-0.1.0/src/conflicts/rules.py +213 -0
- staleguard-0.1.0/src/engineering_demo.py +104 -0
- staleguard-0.1.0/src/eval.py +202 -0
- staleguard-0.1.0/src/main.py +31 -0
- staleguard-0.1.0/src/matching.py +179 -0
- staleguard-0.1.0/src/providers.py +166 -0
- staleguard-0.1.0/src/schema.py +124 -0
- staleguard-0.1.0/src/scorers/__init__.py +17 -0
- staleguard-0.1.0/src/scorers/freshness.py +195 -0
- staleguard-0.1.0/staleguard.egg-info/PKG-INFO +361 -0
- staleguard-0.1.0/staleguard.egg-info/SOURCES.txt +44 -0
- staleguard-0.1.0/staleguard.egg-info/dependency_links.txt +1 -0
- staleguard-0.1.0/staleguard.egg-info/entry_points.txt +2 -0
- staleguard-0.1.0/staleguard.egg-info/requires.txt +11 -0
- staleguard-0.1.0/staleguard.egg-info/top_level.txt +1 -0
- staleguard-0.1.0/tests/test_alternatives.py +62 -0
- staleguard-0.1.0/tests/test_api.py +177 -0
- staleguard-0.1.0/tests/test_auditor.py +267 -0
- staleguard-0.1.0/tests/test_chroma_adapter.py +76 -0
- staleguard-0.1.0/tests/test_conflicts.py +51 -0
- staleguard-0.1.0/tests/test_eval.py +128 -0
- staleguard-0.1.0/tests/test_freshness.py +54 -0
- staleguard-0.1.0/tests/test_json_adapter.py +83 -0
- staleguard-0.1.0/tests/test_langchain_adapter.py +105 -0
- staleguard-0.1.0/tests/test_provenance.py +97 -0
- staleguard-0.1.0/tests/test_schema.py +35 -0
staleguard-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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,361 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: staleguard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Post-retrieval audit layer for RAG pipelines.
|
|
5
|
+
Author: Ishan
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/ish4n10/staleguard
|
|
29
|
+
Project-URL: Repository, https://github.com/ish4n10/staleguard
|
|
30
|
+
Project-URL: Issues, https://github.com/ish4n10/staleguard/issues
|
|
31
|
+
Keywords: rag,retrieval,llm,audit,staleness,nli
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
39
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
40
|
+
Requires-Python: >=3.11
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: numpy>=1.26
|
|
44
|
+
Provides-Extra: local
|
|
45
|
+
Requires-Dist: sentence-transformers>=3.0; extra == "local"
|
|
46
|
+
Provides-Extra: demo
|
|
47
|
+
Requires-Dist: chromadb>=0.5; extra == "demo"
|
|
48
|
+
Provides-Extra: dev
|
|
49
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
50
|
+
Requires-Dist: twine>=5.1; extra == "dev"
|
|
51
|
+
Dynamic: license-file
|
|
52
|
+
|
|
53
|
+
# StaleGuard
|
|
54
|
+
|
|
55
|
+
StaleGuard is a post-retrieval audit layer for RAG pipelines.
|
|
56
|
+
|
|
57
|
+
It sits between retrieval and generation:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
Retriever / Vector DB
|
|
61
|
+
->
|
|
62
|
+
Retrieved chunks
|
|
63
|
+
->
|
|
64
|
+
StaleGuard audit
|
|
65
|
+
->
|
|
66
|
+
Decision:
|
|
67
|
+
FRESH
|
|
68
|
+
STALE
|
|
69
|
+
MIXED
|
|
70
|
+
CONFLICTED
|
|
71
|
+
UNKNOWN
|
|
72
|
+
->
|
|
73
|
+
LLM
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
StaleGuard does not replace retrieval. It checks whether retrieved chunks are trustworthy enough to send to the model.
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install staleguard
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For local embedding and NLI models:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install "staleguard[local]"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
For the repo demos:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install "staleguard[demo]"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## What It Does
|
|
97
|
+
|
|
98
|
+
- score chunk freshness from metadata, version, and date signals
|
|
99
|
+
- find fresher alternatives from a corpus
|
|
100
|
+
- detect contradictions with rules and optional local NLI
|
|
101
|
+
- surface schema issues when retrieved metadata is incomplete
|
|
102
|
+
- return a provenance object you can use in middleware or UI
|
|
103
|
+
|
|
104
|
+
Current trust order:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
metadata > rules = nli
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
That means:
|
|
111
|
+
- explicit metadata wins when it is available
|
|
112
|
+
- rules and NLI are secondary evidence sources
|
|
113
|
+
- if rules and NLI agree, confidence increases
|
|
114
|
+
|
|
115
|
+
## Core API
|
|
116
|
+
|
|
117
|
+
The shortest path is the package-level `audit(...)` function.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from staleguard import audit
|
|
121
|
+
|
|
122
|
+
result = audit(
|
|
123
|
+
query="How do I configure Redis Cluster in Redis 8?",
|
|
124
|
+
retrieved=retriever_output,
|
|
125
|
+
corpus=corpus,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
print(result.verdict)
|
|
129
|
+
print(result.conflicts)
|
|
130
|
+
print(result.provenance)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
If you want to reuse configuration across calls, use `StaleGuard`.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from staleguard import StaleGuard
|
|
137
|
+
|
|
138
|
+
guard = StaleGuard(use_nli=True, block_on_conflict=False)
|
|
139
|
+
result = guard.audit(
|
|
140
|
+
query="How do I configure Redis Cluster in Redis 8?",
|
|
141
|
+
retrieved=retriever_output,
|
|
142
|
+
corpus=corpus,
|
|
143
|
+
)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
For advanced integrations, the same API also accepts:
|
|
147
|
+
|
|
148
|
+
- already-normalized chunk dicts
|
|
149
|
+
- raw Chroma query results
|
|
150
|
+
- LangChain-style documents
|
|
151
|
+
- custom embedding / conflict providers
|
|
152
|
+
|
|
153
|
+
### Normalized chunks
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from staleguard import StaleGuard
|
|
157
|
+
|
|
158
|
+
guard = StaleGuard(use_nli=True)
|
|
159
|
+
|
|
160
|
+
result = guard.audit_chunks(
|
|
161
|
+
query="How do I configure Redis Cluster in Redis 8?",
|
|
162
|
+
retrieved_chunks=[
|
|
163
|
+
{
|
|
164
|
+
"id": "redis_6_cluster_001",
|
|
165
|
+
"text": "Redis 6 uses requirepass configuration for cluster authentication.",
|
|
166
|
+
"product": "redis",
|
|
167
|
+
"topic": "cluster_configuration",
|
|
168
|
+
"version": "6.2",
|
|
169
|
+
"date_ts": 1640995200,
|
|
170
|
+
"source": "redis-6.2-cluster.md",
|
|
171
|
+
"metadata": {"status": "superseded", "superseded_by": "8.0"},
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
corpus=[...],
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`audit(...)` and `guard.audit(...)` auto-detect:
|
|
179
|
+
|
|
180
|
+
- raw Chroma query results
|
|
181
|
+
- LangChain-style documents with `page_content` and `metadata`
|
|
182
|
+
- already-normalized chunk dicts
|
|
183
|
+
|
|
184
|
+
The lower-level helpers still exist for direct use:
|
|
185
|
+
|
|
186
|
+
- `audit_retrieved(...)`
|
|
187
|
+
- `audit_chroma_result(...)`
|
|
188
|
+
- `audit_langchain_docs(...)`
|
|
189
|
+
|
|
190
|
+
## Verdicts
|
|
191
|
+
|
|
192
|
+
- `FRESH`: retrieved context looks current
|
|
193
|
+
- `STALE`: outdated chunks were found
|
|
194
|
+
- `MIXED`: stale and conflicting evidence were found
|
|
195
|
+
- `CONFLICTED`: conflicting evidence was found
|
|
196
|
+
- `UNKNOWN`: metadata is too incomplete to make a strong judgment
|
|
197
|
+
|
|
198
|
+
If you want any conflict to block generation, set:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
block_on_conflict=True
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
That collapses `MIXED` into `CONFLICTED`.
|
|
205
|
+
|
|
206
|
+
## Middleware Pattern
|
|
207
|
+
|
|
208
|
+
This is the intended integration shape:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from staleguard import StaleGuard
|
|
212
|
+
|
|
213
|
+
guard = StaleGuard(use_nli=True, block_on_conflict=False)
|
|
214
|
+
|
|
215
|
+
def audited_retrieve(query: str, retriever_output, corpus: list[dict]):
|
|
216
|
+
audit_result = guard.audit(
|
|
217
|
+
query=query,
|
|
218
|
+
retrieved=retriever_output,
|
|
219
|
+
corpus=corpus,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if audit_result.verdict == "CONFLICTED":
|
|
223
|
+
return {"block": True, "audit": audit_result}
|
|
224
|
+
|
|
225
|
+
return {"block": False, "audit": audit_result}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The application can then:
|
|
229
|
+
|
|
230
|
+
- send `FRESH` chunks to the LLM
|
|
231
|
+
- replace or warn on `STALE`
|
|
232
|
+
- block or escalate on `CONFLICTED`
|
|
233
|
+
- show provenance on `MIXED`
|
|
234
|
+
|
|
235
|
+
## Supported Retrieval Inputs
|
|
236
|
+
|
|
237
|
+
### Chroma
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from staleguard import StaleGuard
|
|
241
|
+
|
|
242
|
+
guard = StaleGuard(use_nli=True)
|
|
243
|
+
|
|
244
|
+
result = guard.audit(
|
|
245
|
+
query=query,
|
|
246
|
+
retrieved=chroma_result,
|
|
247
|
+
corpus=corpus,
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### LangChain-style documents
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from staleguard import StaleGuard
|
|
255
|
+
|
|
256
|
+
guard = StaleGuard(use_nli=True)
|
|
257
|
+
|
|
258
|
+
result = guard.audit(
|
|
259
|
+
query=query,
|
|
260
|
+
retrieved=docs,
|
|
261
|
+
corpus=corpus,
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The repo also exposes adapter helpers:
|
|
266
|
+
|
|
267
|
+
- `normalize_chroma_result(...)`
|
|
268
|
+
- `normalize_langchain_docs(...)`
|
|
269
|
+
- `normalize_chunks(...)`
|
|
270
|
+
|
|
271
|
+
## CLI
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
staleguard audit --query "How do I configure Redis Cluster in Redis 8?" --retrieved retrieved.json --corpus corpus.json
|
|
275
|
+
staleguard eval --corpus eval_cases/kubernetes/corpus.json --cases eval_cases/kubernetes/cases.json --use-nli
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Publish
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
python -m pip install ".[dev]"
|
|
282
|
+
python -m build
|
|
283
|
+
python -m twine upload dist/*
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Chunk Schema
|
|
287
|
+
|
|
288
|
+
Best case input:
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
{
|
|
292
|
+
"id": "redis_8_cluster_001",
|
|
293
|
+
"text": "...",
|
|
294
|
+
"product": "redis",
|
|
295
|
+
"topic": "cluster_configuration",
|
|
296
|
+
"version": "8.0",
|
|
297
|
+
"date_ts": 1735689600,
|
|
298
|
+
"source": "redis-8.0-cluster.md",
|
|
299
|
+
"metadata": {
|
|
300
|
+
"status": "current"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Audit-critical fields:
|
|
306
|
+
|
|
307
|
+
- `text`
|
|
308
|
+
- `product`
|
|
309
|
+
- `topic`
|
|
310
|
+
- `version`
|
|
311
|
+
- `date_ts`
|
|
312
|
+
|
|
313
|
+
If some metadata is missing, StaleGuard will:
|
|
314
|
+
|
|
315
|
+
- infer a few safe fields from `source`
|
|
316
|
+
- record `schema_issues`
|
|
317
|
+
- lower confidence or return `UNKNOWN` when needed
|
|
318
|
+
|
|
319
|
+
## Demos
|
|
320
|
+
|
|
321
|
+
### Redis demo
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
python -m staleguard.chroma_demo
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
This builds a local Chroma collection and shows:
|
|
328
|
+
|
|
329
|
+
- raw Chroma retrieval
|
|
330
|
+
- normalized chunks
|
|
331
|
+
- prepared chunks
|
|
332
|
+
- audit result
|
|
333
|
+
|
|
334
|
+
### Large engineering demo
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
python -m staleguard.engineering_demo
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
This uses a larger corpus around:
|
|
341
|
+
|
|
342
|
+
- loop engineering
|
|
343
|
+
- context engineering
|
|
344
|
+
- tool loop policy
|
|
345
|
+
- memory policy
|
|
346
|
+
- planning
|
|
347
|
+
- retrieval policy
|
|
348
|
+
|
|
349
|
+
The example intentionally includes:
|
|
350
|
+
|
|
351
|
+
- stale 2024/2025 chunks
|
|
352
|
+
- fresher 2026 replacements
|
|
353
|
+
- conflicting guidance that triggers NLI
|
|
354
|
+
|
|
355
|
+
## Current Status
|
|
356
|
+
|
|
357
|
+
The repo is at the local/offline MVP stage.
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# StaleGuard
|
|
2
|
+
|
|
3
|
+
StaleGuard is a post-retrieval audit layer for RAG pipelines.
|
|
4
|
+
|
|
5
|
+
It sits between retrieval and generation:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Retriever / Vector DB
|
|
9
|
+
->
|
|
10
|
+
Retrieved chunks
|
|
11
|
+
->
|
|
12
|
+
StaleGuard audit
|
|
13
|
+
->
|
|
14
|
+
Decision:
|
|
15
|
+
FRESH
|
|
16
|
+
STALE
|
|
17
|
+
MIXED
|
|
18
|
+
CONFLICTED
|
|
19
|
+
UNKNOWN
|
|
20
|
+
->
|
|
21
|
+
LLM
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
StaleGuard does not replace retrieval. It checks whether retrieved chunks are trustworthy enough to send to the model.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install staleguard
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For local embedding and NLI models:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install "staleguard[local]"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For the repo demos:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install "staleguard[demo]"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## What It Does
|
|
45
|
+
|
|
46
|
+
- score chunk freshness from metadata, version, and date signals
|
|
47
|
+
- find fresher alternatives from a corpus
|
|
48
|
+
- detect contradictions with rules and optional local NLI
|
|
49
|
+
- surface schema issues when retrieved metadata is incomplete
|
|
50
|
+
- return a provenance object you can use in middleware or UI
|
|
51
|
+
|
|
52
|
+
Current trust order:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
metadata > rules = nli
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
That means:
|
|
59
|
+
- explicit metadata wins when it is available
|
|
60
|
+
- rules and NLI are secondary evidence sources
|
|
61
|
+
- if rules and NLI agree, confidence increases
|
|
62
|
+
|
|
63
|
+
## Core API
|
|
64
|
+
|
|
65
|
+
The shortest path is the package-level `audit(...)` function.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from staleguard import audit
|
|
69
|
+
|
|
70
|
+
result = audit(
|
|
71
|
+
query="How do I configure Redis Cluster in Redis 8?",
|
|
72
|
+
retrieved=retriever_output,
|
|
73
|
+
corpus=corpus,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
print(result.verdict)
|
|
77
|
+
print(result.conflicts)
|
|
78
|
+
print(result.provenance)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If you want to reuse configuration across calls, use `StaleGuard`.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from staleguard import StaleGuard
|
|
85
|
+
|
|
86
|
+
guard = StaleGuard(use_nli=True, block_on_conflict=False)
|
|
87
|
+
result = guard.audit(
|
|
88
|
+
query="How do I configure Redis Cluster in Redis 8?",
|
|
89
|
+
retrieved=retriever_output,
|
|
90
|
+
corpus=corpus,
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For advanced integrations, the same API also accepts:
|
|
95
|
+
|
|
96
|
+
- already-normalized chunk dicts
|
|
97
|
+
- raw Chroma query results
|
|
98
|
+
- LangChain-style documents
|
|
99
|
+
- custom embedding / conflict providers
|
|
100
|
+
|
|
101
|
+
### Normalized chunks
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from staleguard import StaleGuard
|
|
105
|
+
|
|
106
|
+
guard = StaleGuard(use_nli=True)
|
|
107
|
+
|
|
108
|
+
result = guard.audit_chunks(
|
|
109
|
+
query="How do I configure Redis Cluster in Redis 8?",
|
|
110
|
+
retrieved_chunks=[
|
|
111
|
+
{
|
|
112
|
+
"id": "redis_6_cluster_001",
|
|
113
|
+
"text": "Redis 6 uses requirepass configuration for cluster authentication.",
|
|
114
|
+
"product": "redis",
|
|
115
|
+
"topic": "cluster_configuration",
|
|
116
|
+
"version": "6.2",
|
|
117
|
+
"date_ts": 1640995200,
|
|
118
|
+
"source": "redis-6.2-cluster.md",
|
|
119
|
+
"metadata": {"status": "superseded", "superseded_by": "8.0"},
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
corpus=[...],
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`audit(...)` and `guard.audit(...)` auto-detect:
|
|
127
|
+
|
|
128
|
+
- raw Chroma query results
|
|
129
|
+
- LangChain-style documents with `page_content` and `metadata`
|
|
130
|
+
- already-normalized chunk dicts
|
|
131
|
+
|
|
132
|
+
The lower-level helpers still exist for direct use:
|
|
133
|
+
|
|
134
|
+
- `audit_retrieved(...)`
|
|
135
|
+
- `audit_chroma_result(...)`
|
|
136
|
+
- `audit_langchain_docs(...)`
|
|
137
|
+
|
|
138
|
+
## Verdicts
|
|
139
|
+
|
|
140
|
+
- `FRESH`: retrieved context looks current
|
|
141
|
+
- `STALE`: outdated chunks were found
|
|
142
|
+
- `MIXED`: stale and conflicting evidence were found
|
|
143
|
+
- `CONFLICTED`: conflicting evidence was found
|
|
144
|
+
- `UNKNOWN`: metadata is too incomplete to make a strong judgment
|
|
145
|
+
|
|
146
|
+
If you want any conflict to block generation, set:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
block_on_conflict=True
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
That collapses `MIXED` into `CONFLICTED`.
|
|
153
|
+
|
|
154
|
+
## Middleware Pattern
|
|
155
|
+
|
|
156
|
+
This is the intended integration shape:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from staleguard import StaleGuard
|
|
160
|
+
|
|
161
|
+
guard = StaleGuard(use_nli=True, block_on_conflict=False)
|
|
162
|
+
|
|
163
|
+
def audited_retrieve(query: str, retriever_output, corpus: list[dict]):
|
|
164
|
+
audit_result = guard.audit(
|
|
165
|
+
query=query,
|
|
166
|
+
retrieved=retriever_output,
|
|
167
|
+
corpus=corpus,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if audit_result.verdict == "CONFLICTED":
|
|
171
|
+
return {"block": True, "audit": audit_result}
|
|
172
|
+
|
|
173
|
+
return {"block": False, "audit": audit_result}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The application can then:
|
|
177
|
+
|
|
178
|
+
- send `FRESH` chunks to the LLM
|
|
179
|
+
- replace or warn on `STALE`
|
|
180
|
+
- block or escalate on `CONFLICTED`
|
|
181
|
+
- show provenance on `MIXED`
|
|
182
|
+
|
|
183
|
+
## Supported Retrieval Inputs
|
|
184
|
+
|
|
185
|
+
### Chroma
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from staleguard import StaleGuard
|
|
189
|
+
|
|
190
|
+
guard = StaleGuard(use_nli=True)
|
|
191
|
+
|
|
192
|
+
result = guard.audit(
|
|
193
|
+
query=query,
|
|
194
|
+
retrieved=chroma_result,
|
|
195
|
+
corpus=corpus,
|
|
196
|
+
)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### LangChain-style documents
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from staleguard import StaleGuard
|
|
203
|
+
|
|
204
|
+
guard = StaleGuard(use_nli=True)
|
|
205
|
+
|
|
206
|
+
result = guard.audit(
|
|
207
|
+
query=query,
|
|
208
|
+
retrieved=docs,
|
|
209
|
+
corpus=corpus,
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The repo also exposes adapter helpers:
|
|
214
|
+
|
|
215
|
+
- `normalize_chroma_result(...)`
|
|
216
|
+
- `normalize_langchain_docs(...)`
|
|
217
|
+
- `normalize_chunks(...)`
|
|
218
|
+
|
|
219
|
+
## CLI
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
staleguard audit --query "How do I configure Redis Cluster in Redis 8?" --retrieved retrieved.json --corpus corpus.json
|
|
223
|
+
staleguard eval --corpus eval_cases/kubernetes/corpus.json --cases eval_cases/kubernetes/cases.json --use-nli
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Publish
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
python -m pip install ".[dev]"
|
|
230
|
+
python -m build
|
|
231
|
+
python -m twine upload dist/*
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Chunk Schema
|
|
235
|
+
|
|
236
|
+
Best case input:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
{
|
|
240
|
+
"id": "redis_8_cluster_001",
|
|
241
|
+
"text": "...",
|
|
242
|
+
"product": "redis",
|
|
243
|
+
"topic": "cluster_configuration",
|
|
244
|
+
"version": "8.0",
|
|
245
|
+
"date_ts": 1735689600,
|
|
246
|
+
"source": "redis-8.0-cluster.md",
|
|
247
|
+
"metadata": {
|
|
248
|
+
"status": "current"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Audit-critical fields:
|
|
254
|
+
|
|
255
|
+
- `text`
|
|
256
|
+
- `product`
|
|
257
|
+
- `topic`
|
|
258
|
+
- `version`
|
|
259
|
+
- `date_ts`
|
|
260
|
+
|
|
261
|
+
If some metadata is missing, StaleGuard will:
|
|
262
|
+
|
|
263
|
+
- infer a few safe fields from `source`
|
|
264
|
+
- record `schema_issues`
|
|
265
|
+
- lower confidence or return `UNKNOWN` when needed
|
|
266
|
+
|
|
267
|
+
## Demos
|
|
268
|
+
|
|
269
|
+
### Redis demo
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
python -m staleguard.chroma_demo
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This builds a local Chroma collection and shows:
|
|
276
|
+
|
|
277
|
+
- raw Chroma retrieval
|
|
278
|
+
- normalized chunks
|
|
279
|
+
- prepared chunks
|
|
280
|
+
- audit result
|
|
281
|
+
|
|
282
|
+
### Large engineering demo
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
python -m staleguard.engineering_demo
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
This uses a larger corpus around:
|
|
289
|
+
|
|
290
|
+
- loop engineering
|
|
291
|
+
- context engineering
|
|
292
|
+
- tool loop policy
|
|
293
|
+
- memory policy
|
|
294
|
+
- planning
|
|
295
|
+
- retrieval policy
|
|
296
|
+
|
|
297
|
+
The example intentionally includes:
|
|
298
|
+
|
|
299
|
+
- stale 2024/2025 chunks
|
|
300
|
+
- fresher 2026 replacements
|
|
301
|
+
- conflicting guidance that triggers NLI
|
|
302
|
+
|
|
303
|
+
## Current Status
|
|
304
|
+
|
|
305
|
+
The repo is at the local/offline MVP stage.
|
|
306
|
+
|
|
307
|
+
## License
|
|
308
|
+
|
|
309
|
+
[MIT](LICENSE)
|