pyveil 0.2.2__tar.gz → 0.2.3__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.
- {pyveil-0.2.2 → pyveil-0.2.3}/AGENTS.md +3 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/CHANGELOG.md +17 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/PKG-INFO +71 -2
- {pyveil-0.2.2 → pyveil-0.2.3}/README.md +65 -1
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/README.md +1 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/cookbook.md +93 -8
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/evaluation.html +1 -1
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/index.html +6 -1
- pyveil-0.2.3/docs/integrations/ollama.md +134 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/llms.txt +1 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/manual.html +75 -9
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/release-checklist.md +5 -4
- pyveil-0.2.3/examples/ollama.env.example +11 -0
- pyveil-0.2.3/examples/ollama.example.yaml +15 -0
- pyveil-0.2.3/examples/ollama.py +10 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/llms.txt +3 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyproject.toml +7 -1
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/__init__.py +1 -1
- pyveil-0.2.3/pyveil/integrations/ollama.py +450 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/PKG-INFO +71 -2
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/SOURCES.txt +6 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/requires.txt +6 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_cli.py +16 -1
- pyveil-0.2.3/tests/test_ollama_example.py +381 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/CONTRIBUTING.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/LICENSE +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/MANIFEST.in +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/SECURITY.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/detector-provenance.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/faq.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/index.html +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/mcp-pii-redaction.html +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/python-llm-pii-redaction.html +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/pyveil-vs-presidio.html +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/style.css +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/integrations/logging.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/integrations/mcp.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/integrations/tracing.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/known-limitations.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/media/favicon.svg +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/media/pyveil-awesome-demo.gif +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/media/social-preview.html +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/media/social-preview.png +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/redaction-reference.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/roadmap.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/robots.txt +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/sitemap.xml +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/docs/threat-model.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/evaluation/README.md +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/evaluation/corpus.json +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/evaluation/evaluate.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/azure_openai.env.example +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/azure_openai.example.yaml +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/azure_openai.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/basic_usage.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/custom_rules.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/fastapi_middleware.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/litellm_proxy_filter.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/llm_client_boundary.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/log_filter.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/mcp_server_wrapper.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/mcp_tool_result.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/memory_write.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/openai_agents_guardrail.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/examples/redact_json.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/__main__.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/cli.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/constants.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/core.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/detectors.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/exceptions.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/findings.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/integrations/__init__.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/integrations/azure_openai.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/integrations/logging.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/integrations/mcp.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/levels.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/masker.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/masking.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/patterns.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/placeholders.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/policy.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/py.typed +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/rules.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/utils.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/dependency_links.txt +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/entry_points.txt +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/top_level.txt +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.yaml +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/setup.cfg +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/setup.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/tests/__init__.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_azure_openai_example.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_custom_rules.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_masker.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_patterns.py +0 -0
- {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_utils.py +0 -0
|
@@ -19,6 +19,7 @@ Do not turn pyveil into a general DLP suite, gateway, Presidio clone, or prompt-
|
|
|
19
19
|
- Run CLI demo: `python3 -m pyveil demo`
|
|
20
20
|
- Run CLI locally: `PYVEIL_SECRET=dev-secret python3 -m pyveil redact <file>`
|
|
21
21
|
- Run detector evaluation: `python3 evaluation/evaluate.py --check`
|
|
22
|
+
- Run the Ollama boundary without a model call: `PYVEIL_SECRET=dev-secret python3 -m pyveil.integrations.ollama --dry-run`
|
|
22
23
|
|
|
23
24
|
## Architecture
|
|
24
25
|
|
|
@@ -72,6 +73,7 @@ This repository includes files meant for coding agents and LLM readers:
|
|
|
72
73
|
- [docs/evaluation.html](docs/evaluation.html)
|
|
73
74
|
- [docs/guides/](docs/guides/)
|
|
74
75
|
- [docs/integrations/mcp.md](docs/integrations/mcp.md)
|
|
76
|
+
- [docs/integrations/ollama.md](docs/integrations/ollama.md)
|
|
75
77
|
- [docs/integrations/logging.md](docs/integrations/logging.md)
|
|
76
78
|
- [docs/integrations/tracing.md](docs/integrations/tracing.md)
|
|
77
79
|
|
|
@@ -88,6 +90,7 @@ Examples:
|
|
|
88
90
|
- [examples/fastapi_middleware.py](examples/fastapi_middleware.py)
|
|
89
91
|
- [examples/litellm_proxy_filter.py](examples/litellm_proxy_filter.py)
|
|
90
92
|
- [examples/azure_openai.py](examples/azure_openai.py)
|
|
93
|
+
- [examples/ollama.py](examples/ollama.py)
|
|
91
94
|
|
|
92
95
|
## Development
|
|
93
96
|
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.3 - 2026-07-16
|
|
4
|
+
|
|
5
|
+
Local Ollama boundary release.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Installable Ollama chat integration with env, `.env`, and YAML configuration,
|
|
10
|
+
a dry-run boundary, response metrics, and configurable context/keep-alive limits.
|
|
11
|
+
- A tested `qwen3.5:4b` local-model recipe sized for a 16GB Apple silicon Mac.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Added an optional `ollama` dependency group while keeping the core package
|
|
16
|
+
dependency-free.
|
|
17
|
+
- Expanded the README, Cookbook, web Manual, and agent-facing navigation with
|
|
18
|
+
a complete local-model request path and measured memory/latency trade-offs.
|
|
19
|
+
|
|
3
20
|
## 0.2.2 - 2026-07-15
|
|
4
21
|
|
|
5
22
|
Azure OpenAI adoption release.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: pyveil
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Local PII and secret redaction for Python LLM apps, AI agents, tool calls, and MCP
|
|
5
5
|
Author-email: Hyeon Sang Jeon <wingnut0310@gmail.com>
|
|
6
6
|
Maintainer-email: Hyeon Sang Jeon <wingnut0310@gmail.com>
|
|
@@ -39,6 +39,10 @@ Provides-Extra: azure-openai
|
|
|
39
39
|
Requires-Dist: openai>=1.66.0; extra == "azure-openai"
|
|
40
40
|
Requires-Dist: python-dotenv>=1.0.0; extra == "azure-openai"
|
|
41
41
|
Requires-Dist: PyYAML>=6.0.0; extra == "azure-openai"
|
|
42
|
+
Provides-Extra: ollama
|
|
43
|
+
Requires-Dist: ollama<1.0.0,>=0.6.2; extra == "ollama"
|
|
44
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == "ollama"
|
|
45
|
+
Requires-Dist: PyYAML>=6.0.0; extra == "ollama"
|
|
42
46
|
Provides-Extra: dev
|
|
43
47
|
Requires-Dist: build>=1.2.0; extra == "dev"
|
|
44
48
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
@@ -47,6 +51,7 @@ Requires-Dist: black>=23.0.0; extra == "dev"
|
|
|
47
51
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
48
52
|
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
49
53
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: ollama<1.0.0,>=0.6.2; extra == "dev"
|
|
50
55
|
Requires-Dist: openai>=1.66.0; extra == "dev"
|
|
51
56
|
Requires-Dist: python-dotenv>=1.0.0; extra == "dev"
|
|
52
57
|
Requires-Dist: PyYAML>=6.0.0; extra == "dev"
|
|
@@ -140,6 +145,70 @@ response = call_llm(safe.data) # Your provider SDK call
|
|
|
140
145
|
The provider receives the same list and dictionary shape, with sensitive values
|
|
141
146
|
replaced before serialization or transmission.
|
|
142
147
|
|
|
148
|
+
## Ollama: Local End To End
|
|
149
|
+
|
|
150
|
+
Run a local model behind the same redaction boundary. The optional integration
|
|
151
|
+
uses Ollama's official Python client and defaults to `qwen3.5:4b`, a Q4_K_M
|
|
152
|
+
4.7B model that fits comfortably on a 16GB Apple silicon Mac with a 4K context:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pip install "pyveil[ollama]"
|
|
156
|
+
ollama pull qwen3.5:4b
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from pyveil.integrations.ollama import ask_ollama, load_settings
|
|
161
|
+
|
|
162
|
+
settings = load_settings() # OLLAMA_* + PYVEIL_* environment variables
|
|
163
|
+
result = ask_ollama(
|
|
164
|
+
"Write a follow-up for alice@example.com or 010-1234-5678.",
|
|
165
|
+
settings,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
print(result.redacted_input) # The exact prompt sent to Ollama
|
|
169
|
+
print(result.output_text) # The local model response
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Prove the boundary without loading a model:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
PYVEIL_SECRET=docs-demo-secret \
|
|
176
|
+
python -m pyveil.integrations.ollama --dry-run
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
mode: dry-run
|
|
181
|
+
model: qwen3.5:4b
|
|
182
|
+
host: http://127.0.0.1:11434
|
|
183
|
+
sent-to-ollama: Write a one-sentence support follow-up for [EMAIL:71c6727a7fa2] or [PHONE:b4b889df07ce].
|
|
184
|
+
findings: EMAIL=1, PHONE=1
|
|
185
|
+
ollama-response: skipped (--dry-run)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
For a live local call, set `PYVEIL_SECRET` and run the module. Configuration
|
|
189
|
+
priority is process environment, `.env`, YAML, then defaults:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
PYVEIL_SECRET=a-long-random-hmac-secret \
|
|
193
|
+
python -m pyveil.integrations.ollama
|
|
194
|
+
|
|
195
|
+
python -m pyveil.integrations.ollama \
|
|
196
|
+
--config examples/ollama.example.yaml --env-file .env
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The checked-in [`.env` template](examples/ollama.env.example) and
|
|
200
|
+
[YAML template](examples/ollama.example.yaml) expose model, host, context,
|
|
201
|
+
output length, temperature, timeout, and keep-alive. pyveil caps the default
|
|
202
|
+
context at 4096 and uses `keep_alive=0`, so one-shot calls release model memory;
|
|
203
|
+
set `OLLAMA_KEEP_ALIVE=5m` for faster repeated calls.
|
|
204
|
+
|
|
205
|
+
Observed on this project's M1 Mac mini with 16GB memory and Ollama 0.31.2:
|
|
206
|
+
`qwen3.5:4b` used about 3.2GB at a 4096-token context, a cold request took
|
|
207
|
+
8.1 seconds, and a warm request took 1.3 seconds. These are local measurements,
|
|
208
|
+
not portable performance guarantees. See the
|
|
209
|
+
[Ollama integration guide](docs/integrations/ollama.md) for the full setup and
|
|
210
|
+
memory trade-offs.
|
|
211
|
+
|
|
143
212
|
## Azure OpenAI: End To End
|
|
144
213
|
|
|
145
214
|
Install the optional Azure example dependencies, then load configuration from
|
|
@@ -226,7 +295,7 @@ evaluator:
|
|
|
226
295
|
python evaluation/evaluate.py --check
|
|
227
296
|
```
|
|
228
297
|
|
|
229
|
-
For corpus v1, pyveil 0.2.
|
|
298
|
+
For corpus v1, pyveil 0.2.3 matches all 36 expected findings across 39 cases
|
|
230
299
|
(33 positive, 6 negative), with no corpus false positives, false negatives,
|
|
231
300
|
labeled-value leaks, or non-empty `Finding.raw` values.
|
|
232
301
|
|
|
@@ -81,6 +81,70 @@ response = call_llm(safe.data) # Your provider SDK call
|
|
|
81
81
|
The provider receives the same list and dictionary shape, with sensitive values
|
|
82
82
|
replaced before serialization or transmission.
|
|
83
83
|
|
|
84
|
+
## Ollama: Local End To End
|
|
85
|
+
|
|
86
|
+
Run a local model behind the same redaction boundary. The optional integration
|
|
87
|
+
uses Ollama's official Python client and defaults to `qwen3.5:4b`, a Q4_K_M
|
|
88
|
+
4.7B model that fits comfortably on a 16GB Apple silicon Mac with a 4K context:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install "pyveil[ollama]"
|
|
92
|
+
ollama pull qwen3.5:4b
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from pyveil.integrations.ollama import ask_ollama, load_settings
|
|
97
|
+
|
|
98
|
+
settings = load_settings() # OLLAMA_* + PYVEIL_* environment variables
|
|
99
|
+
result = ask_ollama(
|
|
100
|
+
"Write a follow-up for alice@example.com or 010-1234-5678.",
|
|
101
|
+
settings,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
print(result.redacted_input) # The exact prompt sent to Ollama
|
|
105
|
+
print(result.output_text) # The local model response
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Prove the boundary without loading a model:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
PYVEIL_SECRET=docs-demo-secret \
|
|
112
|
+
python -m pyveil.integrations.ollama --dry-run
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
mode: dry-run
|
|
117
|
+
model: qwen3.5:4b
|
|
118
|
+
host: http://127.0.0.1:11434
|
|
119
|
+
sent-to-ollama: Write a one-sentence support follow-up for [EMAIL:71c6727a7fa2] or [PHONE:b4b889df07ce].
|
|
120
|
+
findings: EMAIL=1, PHONE=1
|
|
121
|
+
ollama-response: skipped (--dry-run)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
For a live local call, set `PYVEIL_SECRET` and run the module. Configuration
|
|
125
|
+
priority is process environment, `.env`, YAML, then defaults:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
PYVEIL_SECRET=a-long-random-hmac-secret \
|
|
129
|
+
python -m pyveil.integrations.ollama
|
|
130
|
+
|
|
131
|
+
python -m pyveil.integrations.ollama \
|
|
132
|
+
--config examples/ollama.example.yaml --env-file .env
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The checked-in [`.env` template](examples/ollama.env.example) and
|
|
136
|
+
[YAML template](examples/ollama.example.yaml) expose model, host, context,
|
|
137
|
+
output length, temperature, timeout, and keep-alive. pyveil caps the default
|
|
138
|
+
context at 4096 and uses `keep_alive=0`, so one-shot calls release model memory;
|
|
139
|
+
set `OLLAMA_KEEP_ALIVE=5m` for faster repeated calls.
|
|
140
|
+
|
|
141
|
+
Observed on this project's M1 Mac mini with 16GB memory and Ollama 0.31.2:
|
|
142
|
+
`qwen3.5:4b` used about 3.2GB at a 4096-token context, a cold request took
|
|
143
|
+
8.1 seconds, and a warm request took 1.3 seconds. These are local measurements,
|
|
144
|
+
not portable performance guarantees. See the
|
|
145
|
+
[Ollama integration guide](docs/integrations/ollama.md) for the full setup and
|
|
146
|
+
memory trade-offs.
|
|
147
|
+
|
|
84
148
|
## Azure OpenAI: End To End
|
|
85
149
|
|
|
86
150
|
Install the optional Azure example dependencies, then load configuration from
|
|
@@ -167,7 +231,7 @@ evaluator:
|
|
|
167
231
|
python evaluation/evaluate.py --check
|
|
168
232
|
```
|
|
169
233
|
|
|
170
|
-
For corpus v1, pyveil 0.2.
|
|
234
|
+
For corpus v1, pyveil 0.2.3 matches all 36 expected findings across 39 cases
|
|
171
235
|
(33 positive, 6 negative), with no corpus false positives, false negatives,
|
|
172
236
|
labeled-value leaks, or non-empty `Finding.raw` values.
|
|
173
237
|
|
|
@@ -12,6 +12,7 @@ Start with:
|
|
|
12
12
|
- [Threat model](threat-model.md)
|
|
13
13
|
- [FAQ](faq.md)
|
|
14
14
|
- [Cookbook](cookbook.md)
|
|
15
|
+
- [Ollama local-model integration](integrations/ollama.md)
|
|
15
16
|
- [Azure OpenAI env/YAML integration](../pyveil/integrations/azure_openai.py)
|
|
16
17
|
- [Redaction reference](redaction-reference.md)
|
|
17
18
|
- [Known limitations](known-limitations.md)
|
|
@@ -48,7 +48,92 @@ response = call_with_redaction(
|
|
|
48
48
|
print(response)
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
## 2.
|
|
51
|
+
## 2. Ollama With A Memory-Aware Local Model
|
|
52
|
+
|
|
53
|
+
Install the optional dependencies and pull the recommended 16GB-Mac model:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install "pyveil[ollama]"
|
|
57
|
+
ollama pull qwen3.5:4b
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The helper redacts before `Client.chat(...)` and returns the exact local-model
|
|
61
|
+
input, response text, and available timing/token metrics:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from pyveil.integrations.ollama import ask_ollama, load_settings
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def answer_with_ollama(prompt: str) -> str:
|
|
68
|
+
settings = load_settings()
|
|
69
|
+
result = ask_ollama(prompt, settings)
|
|
70
|
+
print("sent-to-ollama:", result.redacted_input)
|
|
71
|
+
print("total-ms:", result.total_duration_ms)
|
|
72
|
+
return result.output_text or ""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
answer = answer_with_ollama(
|
|
76
|
+
"Write a follow-up for alice@example.com or 010-1234-5678."
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Under the hood, the boundary stays deliberately small:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from ollama import Client
|
|
84
|
+
from pyveil import Channel, Veil
|
|
85
|
+
|
|
86
|
+
client = Client(host=settings.host, timeout=settings.timeout_seconds)
|
|
87
|
+
veil = Veil.high(
|
|
88
|
+
secret=settings.pyveil_secret,
|
|
89
|
+
scope=settings.pyveil_scope,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
safe = veil.redact_text(prompt, channel=Channel.PROMPT_INPUT)
|
|
93
|
+
response = client.chat(
|
|
94
|
+
model=settings.model,
|
|
95
|
+
messages=[{"role": "user", "content": safe.text}],
|
|
96
|
+
stream=False,
|
|
97
|
+
think=False,
|
|
98
|
+
options={"num_ctx": 4096, "num_predict": 128, "temperature": 0.2},
|
|
99
|
+
keep_alive="0",
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Try the non-model and live paths:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
export PYVEIL_SECRET="a-long-random-hmac-secret"
|
|
107
|
+
|
|
108
|
+
python -m pyveil.integrations.ollama --dry-run
|
|
109
|
+
python -m pyveil.integrations.ollama
|
|
110
|
+
python -m pyveil.integrations.ollama \
|
|
111
|
+
--config examples/ollama.example.yaml --env-file .env
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Configuration priority is process environment, `.env`, YAML, then defaults.
|
|
115
|
+
The default `qwen3.5:4b` configuration uses a 4096-token context and
|
|
116
|
+
`keep_alive=0` to release memory after each response. Use
|
|
117
|
+
`OLLAMA_KEEP_ALIVE=5m` when repeated-call latency matters more than reclaiming
|
|
118
|
+
roughly 3.2GB immediately on a 16GB Apple silicon Mac.
|
|
119
|
+
|
|
120
|
+
Dry-run output from the synthetic default prompt:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
mode: dry-run
|
|
124
|
+
model: qwen3.5:4b
|
|
125
|
+
host: http://127.0.0.1:11434
|
|
126
|
+
sent-to-ollama: Write a one-sentence support follow-up for [EMAIL:71c6727a7fa2] or [PHONE:b4b889df07ce].
|
|
127
|
+
findings: EMAIL=1, PHONE=1
|
|
128
|
+
ollama-response: skipped (--dry-run)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
See [`pyveil/integrations/ollama.py`](../pyveil/integrations/ollama.py),
|
|
132
|
+
[`examples/ollama.env.example`](../examples/ollama.env.example),
|
|
133
|
+
[`examples/ollama.example.yaml`](../examples/ollama.example.yaml), and the
|
|
134
|
+
[full Ollama integration guide](integrations/ollama.md).
|
|
135
|
+
|
|
136
|
+
## 3. Azure OpenAI With Environment Or YAML Configuration
|
|
52
137
|
|
|
53
138
|
Install the optional dependencies:
|
|
54
139
|
|
|
@@ -137,7 +222,7 @@ See [`pyveil/integrations/azure_openai.py`](../pyveil/integrations/azure_openai.
|
|
|
137
222
|
[`examples/azure_openai.env.example`](../examples/azure_openai.env.example), and
|
|
138
223
|
[`examples/azure_openai.example.yaml`](../examples/azure_openai.example.yaml).
|
|
139
224
|
|
|
140
|
-
##
|
|
225
|
+
## 4. Block Credentials Before Model-Controlled Tool Calls
|
|
141
226
|
|
|
142
227
|
The default policy blocks credential-like material in `tool.call.arguments`.
|
|
143
228
|
|
|
@@ -160,7 +245,7 @@ else:
|
|
|
160
245
|
run_tool(**safe_args)
|
|
161
246
|
```
|
|
162
247
|
|
|
163
|
-
##
|
|
248
|
+
## 5. Redact Tool Results Before Returning Them To The Model
|
|
164
249
|
|
|
165
250
|
Tool results often contain customer records, logs, URLs, headers, or hidden tokens.
|
|
166
251
|
|
|
@@ -177,7 +262,7 @@ raw_result = {
|
|
|
177
262
|
safe_result = veil.redact_data(raw_result, channel=Channel.TOOL_CALL_RESULT).data
|
|
178
263
|
```
|
|
179
264
|
|
|
180
|
-
##
|
|
265
|
+
## 6. Redact MCP Resource Content
|
|
181
266
|
|
|
182
267
|
MCP resources can expose files, database rows, logs, or API payloads. Redact before the resource content enters an agent context.
|
|
183
268
|
|
|
@@ -192,7 +277,7 @@ def read_resource_for_agent(resource_id: str) -> object:
|
|
|
192
277
|
return veil.redact_data(raw_resource, channel=Channel.MCP_RESOURCE_CONTENT).data
|
|
193
278
|
```
|
|
194
279
|
|
|
195
|
-
##
|
|
280
|
+
## 7. Redact Memory Before Embedding Or Persistence
|
|
196
281
|
|
|
197
282
|
Memory stores create long-lived recall. Redact before embedding and before persistence.
|
|
198
283
|
|
|
@@ -208,7 +293,7 @@ def write_memory(text: str) -> None:
|
|
|
208
293
|
memory_store.save(text=safe.text, embedding=embedding)
|
|
209
294
|
```
|
|
210
295
|
|
|
211
|
-
##
|
|
296
|
+
## 8. Redact Logs Before Export
|
|
212
297
|
|
|
213
298
|
Use the built-in logging integration for application logs.
|
|
214
299
|
|
|
@@ -224,7 +309,7 @@ logger.addFilter(PyVeilLogFilter(Veil.high(secret=b"log-secret", scope="prod/log
|
|
|
224
309
|
logger.warning("failed request for alice@example.com")
|
|
225
310
|
```
|
|
226
311
|
|
|
227
|
-
##
|
|
312
|
+
## 9. CLI Preflight In CI Or Local Scripts
|
|
228
313
|
|
|
229
314
|
Use `pyveil scan` to count findings and `pyveil redact` to create a redacted artifact.
|
|
230
315
|
|
|
@@ -239,7 +324,7 @@ pyveil redact prompt.txt --channel prompt.input > prompt.safe.txt
|
|
|
239
324
|
pyveil redact request.json --channel tool.call.result --format json > request.safe.json
|
|
240
325
|
```
|
|
241
326
|
|
|
242
|
-
##
|
|
327
|
+
## 10. Redact Known Names And Domain Identifiers
|
|
243
328
|
|
|
244
329
|
The dependency-free core does not guess unknown names. Add values that your
|
|
245
330
|
application already knows are sensitive, plus narrow identifiers from your own
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<p class="eyebrow">Evidence, not promises</p>
|
|
48
48
|
<h1>A reproducible detector <strong>regression evaluation</strong>.</h1>
|
|
49
49
|
<p class="lede">The public corpus tests documented supported shapes, negative examples, labeled-value removal, and the no-raw-findings contract on every change.</p>
|
|
50
|
-
<p class="meta">Corpus v1 · pyveil 0.2.
|
|
50
|
+
<p class="meta">Corpus v1 · pyveil 0.2.3 · Last run July 16, 2026</p>
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
53
53
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"name": "pyveil",
|
|
28
28
|
"applicationCategory": "DeveloperApplication",
|
|
29
29
|
"operatingSystem": "Any",
|
|
30
|
-
"softwareVersion": "0.2.
|
|
30
|
+
"softwareVersion": "0.2.3",
|
|
31
31
|
"programmingLanguage": "Python",
|
|
32
32
|
"description": "Local PII and secret redaction for Python LLM apps and AI agents.",
|
|
33
33
|
"url": "https://hyeonsangjeon.github.io/pyveil/",
|
|
@@ -386,6 +386,11 @@ API key [API_KEY:38ded98a17e7]</code></pre>
|
|
|
386
386
|
<section class="section">
|
|
387
387
|
<h2>Recipes people actually need</h2>
|
|
388
388
|
<div class="grid">
|
|
389
|
+
<a class="recipe" href="manual.html#ollama">
|
|
390
|
+
<h3>Ollama, local and memory-aware</h3>
|
|
391
|
+
<p>Run a tested 4B model on a 16GB Mac and inspect the exact redacted prompt before local inference.</p>
|
|
392
|
+
<span class="more">Read →</span>
|
|
393
|
+
</a>
|
|
389
394
|
<a class="recipe" href="manual.html#azure-openai">
|
|
390
395
|
<h3>Azure OpenAI, end to end</h3>
|
|
391
396
|
<p>Load env or YAML, inspect the exact redacted prompt, then call the Azure v1 Responses API.</p>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Ollama Integration
|
|
2
|
+
|
|
3
|
+
Use `pyveil.integrations.ollama` when a prompt should be redacted immediately
|
|
4
|
+
before it enters a local Ollama model.
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
raw prompt -> pyveil HIGH redaction -> Ollama Client.chat -> local model
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Install the optional official client and configuration dependencies, then pull
|
|
13
|
+
a model:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install "pyveil[ollama]"
|
|
17
|
+
ollama pull qwen3.5:4b
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The integration does not pull a model automatically. Model downloads are large
|
|
21
|
+
and should remain an explicit operator decision.
|
|
22
|
+
|
|
23
|
+
## Why `qwen3.5:4b` For A 16GB Mac
|
|
24
|
+
|
|
25
|
+
The development machine used for this integration is an M1 Mac mini with 16GB
|
|
26
|
+
of unified memory. The selected `qwen3.5:4b` tag is a Q4_K_M model with about
|
|
27
|
+
4.7B parameters and a 3.4GB download. The integration limits context to 4096
|
|
28
|
+
tokens even though the model supports more, because KV cache grows with context.
|
|
29
|
+
|
|
30
|
+
Observed locally with Ollama 0.31.2:
|
|
31
|
+
|
|
32
|
+
| Check | Observed result |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| Loaded model size from `ollama ps` | 3.2GB |
|
|
35
|
+
| Processor | 100% GPU |
|
|
36
|
+
| Context | 4096 tokens |
|
|
37
|
+
| Cold live request | 8.1s total, 5.2s model load |
|
|
38
|
+
| Warm live request | 1.3s total, 0.25s load |
|
|
39
|
+
| Memory free indicator while resident | 26% |
|
|
40
|
+
| Memory free indicator after unload | 57% |
|
|
41
|
+
|
|
42
|
+
These values are a single-machine smoke test, not a benchmark. Background
|
|
43
|
+
applications, prompt length, model updates, and Ollama versions will change
|
|
44
|
+
them.
|
|
45
|
+
|
|
46
|
+
## Dry Run
|
|
47
|
+
|
|
48
|
+
Dry-run proves what would cross the client boundary without loading the model:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
PYVEIL_SECRET=docs-demo-secret \
|
|
52
|
+
python -m pyveil.integrations.ollama --dry-run
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
mode: dry-run
|
|
57
|
+
model: qwen3.5:4b
|
|
58
|
+
host: http://127.0.0.1:11434
|
|
59
|
+
sent-to-ollama: Write a one-sentence support follow-up for [EMAIL:71c6727a7fa2] or [PHONE:b4b889df07ce].
|
|
60
|
+
findings: EMAIL=1, PHONE=1
|
|
61
|
+
ollama-response: skipped (--dry-run)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Live Call
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
export PYVEIL_SECRET="a-long-random-hmac-secret"
|
|
68
|
+
python -m pyveil.integrations.ollama
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Or call the reusable API:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from pyveil.integrations.ollama import ask_ollama, load_settings
|
|
75
|
+
|
|
76
|
+
settings = load_settings()
|
|
77
|
+
result = ask_ollama(
|
|
78
|
+
"Write a support reply for alice@example.com or 010-1234-5678.",
|
|
79
|
+
settings,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
print(result.redacted_input)
|
|
83
|
+
print(result.output_text)
|
|
84
|
+
print(result.total_duration_ms, result.prompt_eval_count, result.eval_count)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`redacted_input` is the exact value passed to `Client.chat`. Findings and
|
|
88
|
+
exceptions do not retain the original email or phone number.
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
Priority is process environment, `.env`, YAML, then defaults.
|
|
93
|
+
|
|
94
|
+
| Environment | YAML key | Default | Purpose |
|
|
95
|
+
| --- | --- | --- | --- |
|
|
96
|
+
| `OLLAMA_HOST` | `ollama.host` | `http://127.0.0.1:11434` | Ollama server URL |
|
|
97
|
+
| `OLLAMA_MODEL` | `ollama.model` | `qwen3.5:4b` | Local model tag |
|
|
98
|
+
| `OLLAMA_NUM_CTX` | `ollama.num_ctx` | `4096` | Context and KV-cache limit |
|
|
99
|
+
| `OLLAMA_NUM_PREDICT` | `ollama.num_predict` | `128` | Maximum output tokens |
|
|
100
|
+
| `OLLAMA_TEMPERATURE` | `ollama.temperature` | `0.2` | Sampling temperature |
|
|
101
|
+
| `OLLAMA_KEEP_ALIVE` | `ollama.keep_alive` | `0` | How long the model stays resident |
|
|
102
|
+
| `OLLAMA_TIMEOUT` | `ollama.timeout_seconds` | `120` | HTTP timeout in seconds |
|
|
103
|
+
| `PYVEIL_SECRET` | `pyveil.secret_env` | required | HMAC secret environment variable |
|
|
104
|
+
| `PYVEIL_SCOPE` | `pyveil.scope` | `ollama/example` | Placeholder namespace |
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
python -m pyveil.integrations.ollama \
|
|
108
|
+
--config examples/ollama.example.yaml \
|
|
109
|
+
--env-file .env
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The pyveil secret cannot be stored directly in YAML. The loader rejects a
|
|
113
|
+
`pyveil.secret` field; YAML may only name the environment variable that holds
|
|
114
|
+
it.
|
|
115
|
+
|
|
116
|
+
## Memory And Latency
|
|
117
|
+
|
|
118
|
+
- Keep the default `OLLAMA_KEEP_ALIVE=0` for one-shot jobs or a memory-busy
|
|
119
|
+
16GB machine. Ollama unloads the model after the response.
|
|
120
|
+
- Set `OLLAMA_KEEP_ALIVE=5m` for repeated agent turns. Warm requests are much
|
|
121
|
+
faster, but the model remains resident during that window.
|
|
122
|
+
- Keep `OLLAMA_NUM_CTX=4096` unless the application proves it needs more.
|
|
123
|
+
- Inspect current residency with `ollama ps`; unload manually with
|
|
124
|
+
`ollama stop qwen3.5:4b`.
|
|
125
|
+
|
|
126
|
+
## Boundary Notes
|
|
127
|
+
|
|
128
|
+
- `127.0.0.1` keeps the redacted request on the machine. A remote
|
|
129
|
+
`OLLAMA_HOST` creates a network boundary even though the raw PII was removed.
|
|
130
|
+
- pyveil protects the input boundary. If model output will be logged, stored,
|
|
131
|
+
or sent onward, redact it again with channel `prompt.output`.
|
|
132
|
+
- Regex and known-value rules do not discover arbitrary names or addresses.
|
|
133
|
+
- The integration disables thinking output and returns only
|
|
134
|
+
`message.content`; it does not persist or expose a reasoning trace.
|
|
@@ -43,6 +43,7 @@ EMAIL, PHONE, CREDIT_CARD, JWT, AUTH_HEADER, PRIVATE_KEY, API_KEY, URL_QUERY_SEC
|
|
|
43
43
|
## Repository documents
|
|
44
44
|
|
|
45
45
|
- Cookbook: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/cookbook.md
|
|
46
|
+
- Ollama integration: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/integrations/ollama.md
|
|
46
47
|
- Redaction reference: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/redaction-reference.md
|
|
47
48
|
- Threat model: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/threat-model.md
|
|
48
49
|
- Known limitations: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/known-limitations.md
|