vlm-guard 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.
- vlm_guard-0.1.0/LICENSE +21 -0
- vlm_guard-0.1.0/PKG-INFO +257 -0
- vlm_guard-0.1.0/README.md +220 -0
- vlm_guard-0.1.0/pyproject.toml +61 -0
- vlm_guard-0.1.0/setup.cfg +4 -0
- vlm_guard-0.1.0/tests/test_analysis.py +29 -0
- vlm_guard-0.1.0/tests/test_engine.py +116 -0
- vlm_guard-0.1.0/tests/test_image_quality.py +35 -0
- vlm_guard-0.1.0/tests/test_parsing.py +64 -0
- vlm_guard-0.1.0/vlm_guard/__init__.py +16 -0
- vlm_guard-0.1.0/vlm_guard/core/__init__.py +0 -0
- vlm_guard-0.1.0/vlm_guard/core/analysis.py +18 -0
- vlm_guard-0.1.0/vlm_guard/core/audit.py +58 -0
- vlm_guard-0.1.0/vlm_guard/core/engine.py +44 -0
- vlm_guard-0.1.0/vlm_guard/core/pipeline.py +69 -0
- vlm_guard-0.1.0/vlm_guard/core/rule.py +33 -0
- vlm_guard-0.1.0/vlm_guard/image/__init__.py +0 -0
- vlm_guard-0.1.0/vlm_guard/image/enhance.py +77 -0
- vlm_guard-0.1.0/vlm_guard/image/quality.py +32 -0
- vlm_guard-0.1.0/vlm_guard/llm/__init__.py +0 -0
- vlm_guard-0.1.0/vlm_guard/llm/loader.py +73 -0
- vlm_guard-0.1.0/vlm_guard/llm/parsing.py +49 -0
- vlm_guard-0.1.0/vlm_guard/output/__init__.py +0 -0
- vlm_guard-0.1.0/vlm_guard/output/report.py +35 -0
- vlm_guard-0.1.0/vlm_guard/prompt/__init__.py +0 -0
- vlm_guard-0.1.0/vlm_guard/prompt/builder.py +34 -0
- vlm_guard-0.1.0/vlm_guard.egg-info/PKG-INFO +257 -0
- vlm_guard-0.1.0/vlm_guard.egg-info/SOURCES.txt +29 -0
- vlm_guard-0.1.0/vlm_guard.egg-info/dependency_links.txt +1 -0
- vlm_guard-0.1.0/vlm_guard.egg-info/requires.txt +15 -0
- vlm_guard-0.1.0/vlm_guard.egg-info/top_level.txt +1 -0
vlm_guard-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohamed Fakhry
|
|
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.
|
vlm_guard-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vlm-guard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Medical Verification Layer for Multimodal LLMs — composable, auditable guardrails for structured LLM outputs
|
|
5
|
+
Author-email: Mohamed Fakhry <mohamed.fakhry@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MohamedFakhry2007/vlm-guard
|
|
8
|
+
Project-URL: Source, https://github.com/MohamedFakhry2007/vlm-guard
|
|
9
|
+
Project-URL: Tracker, https://github.com/MohamedFakhry2007/vlm-guard/issues
|
|
10
|
+
Keywords: llm,multimodal,guardrails,medical-ai,verification,safety,hallucination-prevention
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Requires-Dist: Pillow>=10.0.0
|
|
26
|
+
Provides-Extra: image
|
|
27
|
+
Requires-Dist: torch>=2.0.0; extra == "image"
|
|
28
|
+
Requires-Dist: transformers>=4.40.0; extra == "image"
|
|
29
|
+
Requires-Dist: bitsandbytes>=0.41.0; extra == "image"
|
|
30
|
+
Provides-Extra: all
|
|
31
|
+
Requires-Dist: vlm-guard[image]; extra == "all"
|
|
32
|
+
Requires-Dist: fpdf>=1.7.2; extra == "all"
|
|
33
|
+
Requires-Dist: accelerate>=0.25.0; extra == "all"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# VLM-Guard
|
|
39
|
+
|
|
40
|
+
**Medical Verification Layer for Multimodal LLMs.**
|
|
41
|
+
|
|
42
|
+
VLM-Guard is a framework for building rule-based verification layers that catch physically impossible, biologically inconsistent, or logically contradictory outputs from multimodal LLMs — *before* they reach the end user.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from vlm_guard import GuardrailEngine, BaseRule, RuleResult
|
|
46
|
+
|
|
47
|
+
# 1. Define a rule
|
|
48
|
+
class NoMalariaInTissueRule(BaseRule):
|
|
49
|
+
name = "sample_type_check"
|
|
50
|
+
description = "Malaria requires blood smear, not tissue biopsy"
|
|
51
|
+
|
|
52
|
+
def condition(self, analysis, context):
|
|
53
|
+
return (
|
|
54
|
+
analysis.label == "Malaria"
|
|
55
|
+
and "tissue" in context.get("sample_type", "").lower()
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def action(self, analysis, context):
|
|
59
|
+
analysis.label = "Unclear"
|
|
60
|
+
analysis.confidence = "Low"
|
|
61
|
+
return analysis, RuleResult(
|
|
62
|
+
action_taken=True, action_type="block",
|
|
63
|
+
message="Malaria cannot be diagnosed on tissue biopsy"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# 2. Register and run
|
|
67
|
+
engine = GuardrailEngine()
|
|
68
|
+
engine.register(NoMalariaInTissueRule())
|
|
69
|
+
|
|
70
|
+
final = engine.apply(analysis, context={"sample_type": "Tissue Biopsy"})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Why
|
|
74
|
+
|
|
75
|
+
Multimodal LLMs hallucinate confidently. In medical AI, a "hallucination" isn't a funny caption — it's a biologically impossible diagnosis that could lead to real-world harm.
|
|
76
|
+
|
|
77
|
+
VLM-Guard gives you a **composable, auditable rule engine** to:
|
|
78
|
+
|
|
79
|
+
- ✅ **Block** impossible outputs (malaria in a tissue biopsy)
|
|
80
|
+
- ✅ **Correct** misclassifications (flagellate in blood → trypanosomiasis)
|
|
81
|
+
- ✅ **Promote** clear patterns when the LLM is uncertain
|
|
82
|
+
- ✅ **Flag** ambiguous findings for human review
|
|
83
|
+
- ✅ **Audit** every modification with before/after snapshots
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install vlm-guard
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For image processing extras:
|
|
92
|
+
```bash
|
|
93
|
+
pip install vlm-guard[image]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Quick Start
|
|
97
|
+
|
|
98
|
+
### 1. Define your analysis schema
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from vlm_guard import Analysis
|
|
102
|
+
|
|
103
|
+
result = Analysis(
|
|
104
|
+
label="Malaria",
|
|
105
|
+
confidence="High",
|
|
106
|
+
evidence="Ring forms observed inside RBCs",
|
|
107
|
+
findings="Multiple ring-stage parasites in thin blood smear",
|
|
108
|
+
recommendation="Confirm species with PCR",
|
|
109
|
+
metadata={"severity": "Moderate (++)", "species": "P. falciparum"}
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 2. Build rules
|
|
114
|
+
|
|
115
|
+
Rules have two methods:
|
|
116
|
+
- **`condition(analysis, context)`** — should this rule fire?
|
|
117
|
+
- **`action(analysis, context)`** — what to do when it fires
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from vlm_guard import BaseRule, RuleResult
|
|
121
|
+
|
|
122
|
+
class SizeCheckRule(BaseRule):
|
|
123
|
+
name = "size_check"
|
|
124
|
+
description = "Rejects morphometrically impossible descriptions"
|
|
125
|
+
|
|
126
|
+
def condition(self, analysis, context):
|
|
127
|
+
text = (analysis.findings + " " + analysis.evidence).lower()
|
|
128
|
+
return "7 μm" in text and "macrophage" in text
|
|
129
|
+
|
|
130
|
+
def action(self, analysis, context):
|
|
131
|
+
analysis.label = "Unclear"
|
|
132
|
+
analysis.confidence = "Low"
|
|
133
|
+
return analysis, RuleResult(
|
|
134
|
+
action_taken=True, action_type="block",
|
|
135
|
+
message="RBC-sized structures in macrophage cannot be amastigotes (2-4 μm)"
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 3. Run the guardrail engine
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from vlm_guard import GuardrailEngine
|
|
143
|
+
|
|
144
|
+
engine = GuardrailEngine()
|
|
145
|
+
engine.register(SizeCheckRule())
|
|
146
|
+
|
|
147
|
+
final, audit = engine.apply_with_audit(result, context={"sample_type": "Bone Marrow"})
|
|
148
|
+
print(audit.summary())
|
|
149
|
+
# [{"rule": "size_check", "action": "block", "message": "...", "modified": ...}]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 4. End-to-end pipeline
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from vlm_guard import VLMGuardPipeline
|
|
156
|
+
from vlm_guard.llm.parsing import parse_to_analysis
|
|
157
|
+
from vlm_guard.image.enhance import ImageEnhancer, EnhancementStrategy
|
|
158
|
+
|
|
159
|
+
pipeline = VLMGuardPipeline(
|
|
160
|
+
model_fn=my_llm_inference_fn, # any Callable[[Image, str], str]
|
|
161
|
+
parser_fn=parse_to_analysis, # built-in JSON parser
|
|
162
|
+
guardrail_engine=engine,
|
|
163
|
+
enhancer_fn=ImageEnhancer(EnhancementStrategy.HIGH_CONTRAST),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
result = pipeline.run(image, prompt, context={"sample_type": "Blood Smear"})
|
|
167
|
+
print(result.analysis.label) # final label after guardrails
|
|
168
|
+
print(result.audit.summary()) # everything that changed
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Architecture
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
+-----------+
|
|
175
|
+
| Image |
|
|
176
|
+
+-----+-----+
|
|
177
|
+
|
|
|
178
|
+
v
|
|
179
|
+
+----------+----------+
|
|
180
|
+
| Enhancement (opt) |
|
|
181
|
+
+----------+----------+
|
|
182
|
+
|
|
|
183
|
+
v
|
|
184
|
+
+----------+----------+
|
|
185
|
+
| Multimodal LLM |
|
|
186
|
+
+----------+----------+
|
|
187
|
+
|
|
|
188
|
+
v
|
|
189
|
+
+----------+----------+
|
|
190
|
+
| JSON Parser |
|
|
191
|
+
+----------+----------+
|
|
192
|
+
|
|
|
193
|
+
v
|
|
194
|
+
+----------+----------+
|
|
195
|
+
| Guardrail Engine |
|
|
196
|
+
| +-- Rule 1 (block) |
|
|
197
|
+
| +-- Rule 2 (flag) |
|
|
198
|
+
| +-- Rule 3 (correct)|
|
|
199
|
+
| +-- Audit Trail |
|
|
200
|
+
+----------+----------+
|
|
201
|
+
|
|
|
202
|
+
v
|
|
203
|
+
+----------+----------+
|
|
204
|
+
| Final Analysis |
|
|
205
|
+
+---------------------+
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Plugin System
|
|
209
|
+
|
|
210
|
+
Domain-specific rule packs can be distributed as plugins:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from vlm_guard import GuardrailEngine
|
|
214
|
+
from my_plugin import register_my_rules
|
|
215
|
+
|
|
216
|
+
engine = GuardrailEngine()
|
|
217
|
+
register_my_rules(engine)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Check the `plugins/` directory for built-in examples:
|
|
221
|
+
|
|
222
|
+
- **`ntd_microscopy`** — 12 rule classes for Neglected Tropical Disease microscopy (migrated from [NTD-Assist](https://github.com/MohamedFakhry2007/ntd-assist))
|
|
223
|
+
|
|
224
|
+
## Rules
|
|
225
|
+
|
|
226
|
+
VLM-Guard supports four rule types:
|
|
227
|
+
|
|
228
|
+
| Type | Use Case | Example |
|
|
229
|
+
|------|----------|---------|
|
|
230
|
+
| **Block** | Prevent impossible outputs | Malaria on tissue biopsy → Unclear |
|
|
231
|
+
| **Correct** | Fix misidentifications | Flagellate in macrophage → Leishmaniasis |
|
|
232
|
+
| **Promote** | Upgrade confidence when strong signal | Amastigote + tissue → Leishmaniasis |
|
|
233
|
+
| **Flag** | Lower confidence / append to recommendation | "Ambiguous morphology, manual review suggested" |
|
|
234
|
+
|
|
235
|
+
## Extending
|
|
236
|
+
|
|
237
|
+
VLM-Guard is model-agnostic. The pipeline accepts any `Callable[[Image, str], str]`:
|
|
238
|
+
|
|
239
|
+
- HuggingFace `transformers` models
|
|
240
|
+
- OpenAI / Anthropic API clients
|
|
241
|
+
- Local GGUF inference
|
|
242
|
+
- Mock models for testing
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT
|
|
247
|
+
|
|
248
|
+
## Citation
|
|
249
|
+
|
|
250
|
+
```bibtex
|
|
251
|
+
@software{vlmguard2026,
|
|
252
|
+
title = {VLM-Guard: Verification Layer for Multimodal LLMs},
|
|
253
|
+
author = {Fakhry, Mohamed},
|
|
254
|
+
year = {2026},
|
|
255
|
+
url = {https://github.com/MohamedFakhry2007/vlm-guard}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# VLM-Guard
|
|
2
|
+
|
|
3
|
+
**Medical Verification Layer for Multimodal LLMs.**
|
|
4
|
+
|
|
5
|
+
VLM-Guard is a framework for building rule-based verification layers that catch physically impossible, biologically inconsistent, or logically contradictory outputs from multimodal LLMs — *before* they reach the end user.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from vlm_guard import GuardrailEngine, BaseRule, RuleResult
|
|
9
|
+
|
|
10
|
+
# 1. Define a rule
|
|
11
|
+
class NoMalariaInTissueRule(BaseRule):
|
|
12
|
+
name = "sample_type_check"
|
|
13
|
+
description = "Malaria requires blood smear, not tissue biopsy"
|
|
14
|
+
|
|
15
|
+
def condition(self, analysis, context):
|
|
16
|
+
return (
|
|
17
|
+
analysis.label == "Malaria"
|
|
18
|
+
and "tissue" in context.get("sample_type", "").lower()
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def action(self, analysis, context):
|
|
22
|
+
analysis.label = "Unclear"
|
|
23
|
+
analysis.confidence = "Low"
|
|
24
|
+
return analysis, RuleResult(
|
|
25
|
+
action_taken=True, action_type="block",
|
|
26
|
+
message="Malaria cannot be diagnosed on tissue biopsy"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# 2. Register and run
|
|
30
|
+
engine = GuardrailEngine()
|
|
31
|
+
engine.register(NoMalariaInTissueRule())
|
|
32
|
+
|
|
33
|
+
final = engine.apply(analysis, context={"sample_type": "Tissue Biopsy"})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Why
|
|
37
|
+
|
|
38
|
+
Multimodal LLMs hallucinate confidently. In medical AI, a "hallucination" isn't a funny caption — it's a biologically impossible diagnosis that could lead to real-world harm.
|
|
39
|
+
|
|
40
|
+
VLM-Guard gives you a **composable, auditable rule engine** to:
|
|
41
|
+
|
|
42
|
+
- ✅ **Block** impossible outputs (malaria in a tissue biopsy)
|
|
43
|
+
- ✅ **Correct** misclassifications (flagellate in blood → trypanosomiasis)
|
|
44
|
+
- ✅ **Promote** clear patterns when the LLM is uncertain
|
|
45
|
+
- ✅ **Flag** ambiguous findings for human review
|
|
46
|
+
- ✅ **Audit** every modification with before/after snapshots
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install vlm-guard
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For image processing extras:
|
|
55
|
+
```bash
|
|
56
|
+
pip install vlm-guard[image]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
### 1. Define your analysis schema
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from vlm_guard import Analysis
|
|
65
|
+
|
|
66
|
+
result = Analysis(
|
|
67
|
+
label="Malaria",
|
|
68
|
+
confidence="High",
|
|
69
|
+
evidence="Ring forms observed inside RBCs",
|
|
70
|
+
findings="Multiple ring-stage parasites in thin blood smear",
|
|
71
|
+
recommendation="Confirm species with PCR",
|
|
72
|
+
metadata={"severity": "Moderate (++)", "species": "P. falciparum"}
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Build rules
|
|
77
|
+
|
|
78
|
+
Rules have two methods:
|
|
79
|
+
- **`condition(analysis, context)`** — should this rule fire?
|
|
80
|
+
- **`action(analysis, context)`** — what to do when it fires
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from vlm_guard import BaseRule, RuleResult
|
|
84
|
+
|
|
85
|
+
class SizeCheckRule(BaseRule):
|
|
86
|
+
name = "size_check"
|
|
87
|
+
description = "Rejects morphometrically impossible descriptions"
|
|
88
|
+
|
|
89
|
+
def condition(self, analysis, context):
|
|
90
|
+
text = (analysis.findings + " " + analysis.evidence).lower()
|
|
91
|
+
return "7 μm" in text and "macrophage" in text
|
|
92
|
+
|
|
93
|
+
def action(self, analysis, context):
|
|
94
|
+
analysis.label = "Unclear"
|
|
95
|
+
analysis.confidence = "Low"
|
|
96
|
+
return analysis, RuleResult(
|
|
97
|
+
action_taken=True, action_type="block",
|
|
98
|
+
message="RBC-sized structures in macrophage cannot be amastigotes (2-4 μm)"
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Run the guardrail engine
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from vlm_guard import GuardrailEngine
|
|
106
|
+
|
|
107
|
+
engine = GuardrailEngine()
|
|
108
|
+
engine.register(SizeCheckRule())
|
|
109
|
+
|
|
110
|
+
final, audit = engine.apply_with_audit(result, context={"sample_type": "Bone Marrow"})
|
|
111
|
+
print(audit.summary())
|
|
112
|
+
# [{"rule": "size_check", "action": "block", "message": "...", "modified": ...}]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 4. End-to-end pipeline
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from vlm_guard import VLMGuardPipeline
|
|
119
|
+
from vlm_guard.llm.parsing import parse_to_analysis
|
|
120
|
+
from vlm_guard.image.enhance import ImageEnhancer, EnhancementStrategy
|
|
121
|
+
|
|
122
|
+
pipeline = VLMGuardPipeline(
|
|
123
|
+
model_fn=my_llm_inference_fn, # any Callable[[Image, str], str]
|
|
124
|
+
parser_fn=parse_to_analysis, # built-in JSON parser
|
|
125
|
+
guardrail_engine=engine,
|
|
126
|
+
enhancer_fn=ImageEnhancer(EnhancementStrategy.HIGH_CONTRAST),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
result = pipeline.run(image, prompt, context={"sample_type": "Blood Smear"})
|
|
130
|
+
print(result.analysis.label) # final label after guardrails
|
|
131
|
+
print(result.audit.summary()) # everything that changed
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Architecture
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
+-----------+
|
|
138
|
+
| Image |
|
|
139
|
+
+-----+-----+
|
|
140
|
+
|
|
|
141
|
+
v
|
|
142
|
+
+----------+----------+
|
|
143
|
+
| Enhancement (opt) |
|
|
144
|
+
+----------+----------+
|
|
145
|
+
|
|
|
146
|
+
v
|
|
147
|
+
+----------+----------+
|
|
148
|
+
| Multimodal LLM |
|
|
149
|
+
+----------+----------+
|
|
150
|
+
|
|
|
151
|
+
v
|
|
152
|
+
+----------+----------+
|
|
153
|
+
| JSON Parser |
|
|
154
|
+
+----------+----------+
|
|
155
|
+
|
|
|
156
|
+
v
|
|
157
|
+
+----------+----------+
|
|
158
|
+
| Guardrail Engine |
|
|
159
|
+
| +-- Rule 1 (block) |
|
|
160
|
+
| +-- Rule 2 (flag) |
|
|
161
|
+
| +-- Rule 3 (correct)|
|
|
162
|
+
| +-- Audit Trail |
|
|
163
|
+
+----------+----------+
|
|
164
|
+
|
|
|
165
|
+
v
|
|
166
|
+
+----------+----------+
|
|
167
|
+
| Final Analysis |
|
|
168
|
+
+---------------------+
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Plugin System
|
|
172
|
+
|
|
173
|
+
Domain-specific rule packs can be distributed as plugins:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from vlm_guard import GuardrailEngine
|
|
177
|
+
from my_plugin import register_my_rules
|
|
178
|
+
|
|
179
|
+
engine = GuardrailEngine()
|
|
180
|
+
register_my_rules(engine)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Check the `plugins/` directory for built-in examples:
|
|
184
|
+
|
|
185
|
+
- **`ntd_microscopy`** — 12 rule classes for Neglected Tropical Disease microscopy (migrated from [NTD-Assist](https://github.com/MohamedFakhry2007/ntd-assist))
|
|
186
|
+
|
|
187
|
+
## Rules
|
|
188
|
+
|
|
189
|
+
VLM-Guard supports four rule types:
|
|
190
|
+
|
|
191
|
+
| Type | Use Case | Example |
|
|
192
|
+
|------|----------|---------|
|
|
193
|
+
| **Block** | Prevent impossible outputs | Malaria on tissue biopsy → Unclear |
|
|
194
|
+
| **Correct** | Fix misidentifications | Flagellate in macrophage → Leishmaniasis |
|
|
195
|
+
| **Promote** | Upgrade confidence when strong signal | Amastigote + tissue → Leishmaniasis |
|
|
196
|
+
| **Flag** | Lower confidence / append to recommendation | "Ambiguous morphology, manual review suggested" |
|
|
197
|
+
|
|
198
|
+
## Extending
|
|
199
|
+
|
|
200
|
+
VLM-Guard is model-agnostic. The pipeline accepts any `Callable[[Image, str], str]`:
|
|
201
|
+
|
|
202
|
+
- HuggingFace `transformers` models
|
|
203
|
+
- OpenAI / Anthropic API clients
|
|
204
|
+
- Local GGUF inference
|
|
205
|
+
- Mock models for testing
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
|
210
|
+
|
|
211
|
+
## Citation
|
|
212
|
+
|
|
213
|
+
```bibtex
|
|
214
|
+
@software{vlmguard2026,
|
|
215
|
+
title = {VLM-Guard: Verification Layer for Multimodal LLMs},
|
|
216
|
+
author = {Fakhry, Mohamed},
|
|
217
|
+
year = {2026},
|
|
218
|
+
url = {https://github.com/MohamedFakhry2007/vlm-guard}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vlm-guard"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Medical Verification Layer for Multimodal LLMs — composable, auditable guardrails for structured LLM outputs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "Mohamed Fakhry", email = "mohamed.fakhry@example.com"},
|
|
13
|
+
]
|
|
14
|
+
keywords = [
|
|
15
|
+
"llm", "multimodal", "guardrails", "medical-ai",
|
|
16
|
+
"verification", "safety", "hallucination-prevention",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
28
|
+
"Topic :: Scientific/Engineering :: Medical Science Apps.",
|
|
29
|
+
]
|
|
30
|
+
requires-python = ">=3.10"
|
|
31
|
+
dependencies = [
|
|
32
|
+
"pydantic>=2.0.0",
|
|
33
|
+
"Pillow>=10.0.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
image = [
|
|
38
|
+
"torch>=2.0.0",
|
|
39
|
+
"transformers>=4.40.0",
|
|
40
|
+
"bitsandbytes>=0.41.0",
|
|
41
|
+
]
|
|
42
|
+
all = [
|
|
43
|
+
"vlm-guard[image]",
|
|
44
|
+
"fpdf>=1.7.2",
|
|
45
|
+
"accelerate>=0.25.0",
|
|
46
|
+
]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest>=7.0.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://github.com/MohamedFakhry2007/vlm-guard"
|
|
53
|
+
Source = "https://github.com/MohamedFakhry2007/vlm-guard"
|
|
54
|
+
Tracker = "https://github.com/MohamedFakhry2007/vlm-guard/issues"
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
include = ["vlm_guard*"]
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
testpaths = ["tests"]
|
|
61
|
+
python_files = ["test_*.py"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from vlm_guard.core.analysis import Analysis
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_analysis_defaults():
|
|
5
|
+
a = Analysis(label="Test", confidence="High")
|
|
6
|
+
assert a.label == "Test"
|
|
7
|
+
assert a.confidence == "High"
|
|
8
|
+
assert a.evidence == ""
|
|
9
|
+
assert a.findings == ""
|
|
10
|
+
assert a.recommendation == ""
|
|
11
|
+
assert a.metadata == {}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_analysis_metadata():
|
|
15
|
+
a = Analysis(label="Test", confidence="Medium", metadata={"species": "P. falciparum"})
|
|
16
|
+
assert a.metadata["species"] == "P. falciparum"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_analysis_forbids_extra_fields():
|
|
20
|
+
import pytest
|
|
21
|
+
with pytest.raises(ValueError):
|
|
22
|
+
Analysis(label="Test", confidence="High", nonexistent_field="should fail")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_analysis_dump():
|
|
26
|
+
a = Analysis(label="X", confidence="Low", evidence="proof", findings="desc", recommendation="action")
|
|
27
|
+
d = a.model_dump()
|
|
28
|
+
assert d["label"] == "X"
|
|
29
|
+
assert d["confidence"] == "Low"
|